/*
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html.
 *
 * This file is a derivative of code released by the University of
 * California under the terms listed below.
 *
 * WALA JDT Frontend is Copyright (c) 2008 The Regents of the
 * University of California (Regents). Provided that this notice and
 * the following two paragraphs are included in any distribution of
 * Refinement Analysis Tools or its derivative work, Regents agrees
 * not to assert any of Regents' copyright rights in Refinement
 * Analysis Tools against recipient for recipient's reproduction,
 * preparation of derivative works, public display, public
 * performance, distribution or sublicensing of Refinement Analysis
 * Tools and derivative works, in source code and object code form.
 * This agreement not to assert does not confer, by implication,
 * estoppel, or otherwise any license or rights in any intellectual
 * property of Regents, including, but not limited to, any patents
 * of Regents or Regents' employees.
 *
 * IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT,
 * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
 * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE
 * AND ITS DOCUMENTATION, EVEN IF REGENTS HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE AND FURTHER DISCLAIMS ANY STATUTORY
 * WARRANTY OF NON-INFRINGEMENT. THE SOFTWARE AND ACCOMPANYING
 * DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS
 * IS". REGENTS HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT,
 * UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 */
package com.ibm.wala.cast.java.translator.jdt;

import com.ibm.wala.analysis.typeInference.JavaPrimitiveType;
import com.ibm.wala.cast.ir.translator.AstTranslator.InternalCAstSymbol;
import com.ibm.wala.cast.ir.translator.TranslatorToCAst;
import com.ibm.wala.cast.ir.translator.TranslatorToCAst.DoLoopTranslator;
import com.ibm.wala.cast.java.loader.JavaSourceLoaderImpl;
import com.ibm.wala.cast.java.loader.Util;
import com.ibm.wala.cast.java.translator.JavaProcedureEntity;
import com.ibm.wala.cast.tree.CAst;
import com.ibm.wala.cast.tree.CAstAnnotation;
import com.ibm.wala.cast.tree.CAstControlFlowMap;
import com.ibm.wala.cast.tree.CAstEntity;
import com.ibm.wala.cast.tree.CAstNode;
import com.ibm.wala.cast.tree.CAstNodeTypeMap;
import com.ibm.wala.cast.tree.CAstQualifier;
import com.ibm.wala.cast.tree.CAstSourcePositionMap;
import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position;
import com.ibm.wala.cast.tree.CAstType;
import com.ibm.wala.cast.tree.impl.CAstControlFlowRecorder;
import com.ibm.wala.cast.tree.impl.CAstImpl;
import com.ibm.wala.cast.tree.impl.CAstNodeTypeMapRecorder;
import com.ibm.wala.cast.tree.impl.CAstOperator;
import com.ibm.wala.cast.tree.impl.CAstSourcePositionRecorder;
import com.ibm.wala.cast.tree.impl.CAstSymbolImpl;
import com.ibm.wala.cast.util.CAstPrinter;
import com.ibm.wala.classLoader.CallSiteReference;
import com.ibm.wala.shrike.shrikeBT.IInvokeInstruction;
import com.ibm.wala.types.FieldReference;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.types.TypeName;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.collections.EmptyIterator;
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.collections.Pair;
import com.ibm.wala.util.debug.Assertions;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.ArrayAccess;
import org.eclipse.jdt.core.dom.ArrayCreation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.AssertStatement;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.BooleanLiteral;
import org.eclipse.jdt.core.dom.BreakStatement;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.CatchClause;
import org.eclipse.jdt.core.dom.CharacterLiteral;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ConditionalExpression;
import org.eclipse.jdt.core.dom.ConstructorInvocation;
import org.eclipse.jdt.core.dom.ContinueStatement;
import org.eclipse.jdt.core.dom.DoStatement;
import org.eclipse.jdt.core.dom.EmptyStatement;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ForStatement;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.Initializer;
import org.eclipse.jdt.core.dom.InstanceofExpression;
import org.eclipse.jdt.core.dom.LabeledStatement;
import org.eclipse.jdt.core.dom.LambdaExpression;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.NullLiteral;
import org.eclipse.jdt.core.dom.NumberLiteral;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.PostfixExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.SuperConstructorInvocation;
import org.eclipse.jdt.core.dom.SuperFieldAccess;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.dom.SwitchCase;
import org.eclipse.jdt.core.dom.SwitchStatement;
import org.eclipse.jdt.core.dom.SynchronizedStatement;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.ThrowStatement;
import org.eclipse.jdt.core.dom.TryStatement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclarationStatement;
import org.eclipse.jdt.core.dom.TypeLiteral;
import org.eclipse.jdt.core.dom.UnionType;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.WhileStatement;

// TO TEST:
// "1/0" surrounded by catch ArithmeticException & RunTimeException (TryCatchContext.getCatchTypes"
// another subtype of ArithmeticException surrounded by catch ArithmeticException
// binary ops with all kinds of type conversions
// simplenames: fields of this, fields of an enclosing class, fields of an enclosing method, static
// fields, fully qualified fields w/ package stuff
// exceptional CFG edges, somehow. call nodes, new nodes, division by zero in binary ops, null
// pointer in field accesses, etc.
// implicit constructors

// FIXME 1.4: think about / ask about TAGALONG (JDT stuff tagging along in memory cos we keep it).
// FIXME 1.4: find LEFTOUT and find out why polyglot has extra code / infrastructure, if it's used
// and what for, etc.

// Java 1.6:
// * type parameters/generics: see getArgumentTypes in ProcedureEntity$anon. also anywhere we use
// ITypeBinding.equals() may be affected
//   see anywhere in code labeled GENERICS for present "treat as raw types"/"pretend it's 1.4 and
// casts" solution.
// * boxing (YUCK). see resolveBoxing()
// * enums (probably in simplename or something. but using resolveConstantExpressionValue()
// possible)

public abstract class JDTJava2CAstTranslator<T extends Position> {
  protected boolean dump;

  protected final CAst fFactory = new CAstImpl();

  // ///////////////////////////////////////////
  // / HANDLINGS OF VARIOUS THINGS //
  // ///////////////////////////////////////////
  protected final AST ast; // TAGALONG

  protected final JDTIdentityMapper fIdentityMapper; // TAGALONG

  protected final JDTTypeDictionary fTypeDict;

  protected final JavaSourceLoaderImpl fSourceLoader;

  protected final ITypeBinding fDivByZeroExcType;

  protected final ITypeBinding fNullPointerExcType;

  protected final ITypeBinding fClassCastExcType;

  protected final ITypeBinding fRuntimeExcType;

  protected final ITypeBinding NoClassDefFoundError;

  protected final ITypeBinding ExceptionInInitializerError;

  protected final ITypeBinding OutOfMemoryError;

  protected final DoLoopTranslator doLoopTranslator;

  protected final String fullPath;

  protected final CompilationUnit cu;

  //
  // COMPILATION UNITS & TYPES
  //

  public JDTJava2CAstTranslator(
      JavaSourceLoaderImpl sourceLoader,
      CompilationUnit astRoot,
      String fullPath,
      boolean replicateForDoLoops) {
    this(sourceLoader, astRoot, fullPath, replicateForDoLoops, false);
  }

  public JDTJava2CAstTranslator(
      JavaSourceLoaderImpl sourceLoader,
      CompilationUnit astRoot,
      String fullPath,
      boolean replicateForDoLoops,
      boolean dump) {
    fDivByZeroExcType = FakeExceptionTypeBinding.arithmetic;
    fNullPointerExcType = FakeExceptionTypeBinding.nullPointer;
    fClassCastExcType = FakeExceptionTypeBinding.classCast;
    NoClassDefFoundError = FakeExceptionTypeBinding.noClassDef;
    ExceptionInInitializerError = FakeExceptionTypeBinding.initException;
    OutOfMemoryError = FakeExceptionTypeBinding.outOfMemory;

    this.fSourceLoader = sourceLoader;
    this.cu = astRoot;

    this.fullPath = fullPath;
    this.ast = astRoot.getAST();

    this.doLoopTranslator = new DoLoopTranslator(replicateForDoLoops, fFactory);

    this.dump = dump;

    // FIXME: we might need one AST (-> "Object" class) for all files.
    fIdentityMapper = new JDTIdentityMapper(fSourceLoader.getReference(), ast);
    fTypeDict = new JDTTypeDictionary(ast, fIdentityMapper);

    fRuntimeExcType = ast.resolveWellKnownType("java.lang.RuntimeException");
    assert fRuntimeExcType != null;
  }

  public CAstEntity translateToCAst() {

    List<CAstEntity> declEntities = new ArrayList<>();

    for (AbstractTypeDeclaration decl : (Iterable<AbstractTypeDeclaration>) cu.types()) {
      // can be of type AnnotationTypeDeclaration, EnumDeclaration, TypeDeclaration
      declEntities.add(visit(decl, new RootContext()));
    }

    if (dump) {
      for (CAstEntity d : declEntities) {
        CAstPrinter.printTo(d, new PrintWriter(System.err));
      }
    }

    return new CompilationUnitEntity(cu.getPackage(), declEntities);
  }

  //
  // TYPES
  //

  protected class ClassEntity implements CAstEntity {
    // TAGALONG (not static, will keep reference to ast, fIdentityMapper, etc)

    @Override
    public Position getPosition(int arg) {
      return null;
    }

    private final String fName;

    private final Collection<CAstQualifier> fQuals;

    private final Collection<CAstEntity> fEntities;

    private final ITypeBinding fJdtType; // TAGALONG

    private final T fSourcePosition;

    private final T fNamePos;

    public ClassEntity(
        ITypeBinding jdtType,
        String name,
        Collection<CAstQualifier> quals,
        Collection<CAstEntity> entities,
        T pos,
        T namePos) {
      fNamePos = namePos;
      fName = name;
      fQuals = quals;
      fEntities = entities;
      fJdtType = jdtType;
      fSourcePosition = pos;
    }

    @Override
    public Collection<CAstAnnotation> getAnnotations() {
      // TODO Auto-generated method stub
      return null;
    }

    @Override
    public int getKind() {
      return TYPE_ENTITY;
    }

    @Override
    public String getName() {
      return fName; // unqualified?
    }

    @Override
    public String getSignature() {
      return 'L' + fName.replace('.', '/') + ';';
    }

    @Override
    public String[] getArgumentNames() {
      return new String[0];
    }

    @Override
    public CAstNode[] getArgumentDefaults() {
      return new CAstNode[0];
    }

    @Override
    public int getArgumentCount() {
      return 0;
    }

    @Override
    public CAstNode getAST() {
      // This entity has no AST nodes, really.
      return null;
    }

    @Override
    public Map<CAstNode, Collection<CAstEntity>> getAllScopedEntities() {
      return Collections.singletonMap(null, fEntities);
    }

    @Override
    public Iterator<CAstEntity> getScopedEntities(CAstNode construct) {
      Assertions.UNREACHABLE(
          "Non-AST-bearing entity (ClassEntity) asked for scoped entities related to a given AST node");
      return null;
    }

    @Override
    public CAstControlFlowMap getControlFlow() {
      // This entity has no AST nodes, really.
      return null;
    }

    @Override
    public CAstSourcePositionMap getSourceMap() {
      // This entity has no AST nodes, really.
      return null;
    }

    @Override
    public CAstSourcePositionMap.Position getPosition() {
      return fSourcePosition;
    }

    @Override
    public CAstNodeTypeMap getNodeTypeMap() {
      // This entity has no AST nodes, really.
      return new CAstNodeTypeMap() {
        @Override
        public CAstType getNodeType(CAstNode node) {
          throw new UnsupportedOperationException();
        }

        @Override
        public Collection<CAstNode> getMappedNodes() {
          throw new UnsupportedOperationException();
        }
      };
    }

    @Override
    public Collection<CAstQualifier> getQualifiers() {
      return fQuals;
    }

    @Override
    public CAstType getType() {
      return fTypeDict.new JdtJavaType(fJdtType);
    }

    @Override
    public Position getNamePosition() {
      return fNamePos;
    }
  }

  private static boolean isInterface(AbstractTypeDeclaration decl) {
    return decl instanceof AnnotationTypeDeclaration
        || (decl instanceof TypeDeclaration && ((TypeDeclaration) decl).isInterface());
  }

  private CAstEntity visitTypeDecl(AbstractTypeDeclaration n, WalkContext context) {
    return createClassDeclaration(
        n,
        n.bodyDeclarations(),
        null,
        n.resolveBinding(),
        n.getName().getIdentifier(),
        n.getModifiers(),
        isInterface(n),
        n instanceof AnnotationTypeDeclaration,
        context,
        makePosition(n.getName()));
  }

  /**
   * @param name Used in creating default constructor, and passed into new ClassEntity()
   */
  private CAstEntity createClassDeclaration(
      ASTNode n,
      List<BodyDeclaration> bodyDecls,
      List<EnumConstantDeclaration> enumConstants,
      ITypeBinding typeBinding,
      String name,
      int modifiers,
      boolean isInterface,
      boolean isAnnotation,
      WalkContext context,
      T namePos) {
    final List<CAstEntity> memberEntities = new ArrayList<>();

    // find and collect all initializers (type Initializer) and field initializers (type
    // VariableDeclarationFragment).
    // instance initializer code will be inserted into each constructors.
    // all static initializer code will be grouped together in its own entity.
    ArrayList<ASTNode> inits = new ArrayList<>();
    ArrayList<ASTNode> staticInits = new ArrayList<>();

    if (enumConstants != null) {
      // always (implicitly) static,final (actually, no modifiers allowed)
      staticInits.addAll(enumConstants);
    }

    for (BodyDeclaration decl : bodyDecls) {
      if (decl instanceof Initializer) {
        Initializer initializer = (Initializer) decl;
        boolean isStatic = ((initializer.getModifiers() & Modifier.STATIC) != 0);
        (isStatic ? staticInits : inits).add(initializer);
      } else if (decl instanceof FieldDeclaration) {
        FieldDeclaration fd = (FieldDeclaration) decl;

        for (VariableDeclarationFragment frag :
            (Iterable<VariableDeclarationFragment>) fd.fragments()) {
          if (frag.getInitializer() != null) {
            boolean isStatic = ((fd.getModifiers() & Modifier.STATIC) != 0);
            (isStatic ? staticInits : inits).add(frag);
          }
        }
      }
    }

    // process entities. initializers will be folded in here.
    if (enumConstants != null) {
      for (EnumConstantDeclaration decl : enumConstants) {
        memberEntities.add(visit(decl));
      }
    }

    for (BodyDeclaration decl : bodyDecls) {
      if (decl instanceof FieldDeclaration) {
        FieldDeclaration fieldDecl = (FieldDeclaration) decl;
        Collection<CAstQualifier> quals =
            JDT2CAstUtils.mapModifiersToQualifiers(fieldDecl.getModifiers(), false, false);
        for (VariableDeclarationFragment fieldFrag :
            (Iterable<VariableDeclarationFragment>) fieldDecl.fragments()) {
          IVariableBinding fieldBinding = fieldFrag.resolveBinding();
          memberEntities.add(
              new FieldEntity(
                  fieldFrag.getName().getIdentifier(),
                  fieldBinding.getType(),
                  quals,
                  makePosition(
                      fieldFrag.getStartPosition(),
                      fieldFrag.getStartPosition() + fieldFrag.getLength()),
                  handleAnnotations(fieldBinding),
                  makePosition(fieldFrag.getName())));
        }
      } else if (decl instanceof Initializer) {
        // Initializers are inserted into constructors when making constructors.
      } else if (decl instanceof MethodDeclaration) {
        MethodDeclaration metDecl = (MethodDeclaration) decl;

        if (typeBinding.isEnum() && metDecl.isConstructor())
          memberEntities.add(
              createEnumConstructorWithParameters(
                  metDecl.resolveBinding(), metDecl, context, inits, metDecl));
        else {
          memberEntities.add(visit(metDecl, typeBinding, context, inits));

          // /////////////// Java 1.5 "overridden with subtype" thing (covariant return type)
          // ///////////
          Collection<IMethodBinding> overriddenMets =
              JDT2CAstUtils.getOverriddenMethod(metDecl.resolveBinding());
          if (overriddenMets != null) {
            for (IMethodBinding overridden : overriddenMets)
              if (!JDT2CAstUtils.sameErasedSignatureAndReturnType(
                  metDecl.resolveBinding(), overridden))
                memberEntities.add(
                    makeSyntheticCovariantRedirect(
                        metDecl, metDecl.resolveBinding(), overridden, context));
          }
        }
      } else if (decl instanceof AbstractTypeDeclaration) {
        memberEntities.add(visit((AbstractTypeDeclaration) decl, context));
      } else if (decl instanceof AnnotationTypeMemberDeclaration) {
        // TODO: need to decide what to do with these
      } else {
        Assertions.UNREACHABLE("BodyDeclaration not Field, Initializer, or Method");
      }
    }

    // add default constructor(s) if necessary
    // most default constructors have no parameters; however, those created by anonymous classes
    // will have parameters
    // (they just call super with those parameters)
    for (IMethodBinding met : typeBinding.getDeclaredMethods()) {
      if (met.isDefaultConstructor()) {
        if (typeBinding.isEnum())
          memberEntities.add(createEnumConstructorWithParameters(met, n, context, inits, null));
        else if (met.getParameterTypes().length > 0)
          memberEntities.add(createDefaultConstructorWithParameters(met, n, context, inits));
        else memberEntities.add(createDefaultConstructor(typeBinding, context, inits, n));
      }
    }

    if (typeBinding.isEnum() && !typeBinding.isAnonymous())
      doEnumHiddenEntities(typeBinding, memberEntities, context);

    // collect static inits
    if (!staticInits.isEmpty()) {
      Map<CAstNode, CAstEntity> childEntities = HashMapFactory.make();
      final MethodContext newContext = new MethodContext(context, childEntities);
      // childEntities is the same one as in the ProcedureEntity. later visit(New), etc. may add to
      // this.

      List<CAstNode> bodyNodes = new ArrayList<>(staticInits.size());
      for (ASTNode staticInit : staticInits)
        bodyNodes.add(visitFieldInitNode(staticInit, newContext));
      CAstNode staticInitAst = makeNode(newContext, fFactory, n, CAstNode.BLOCK_STMT, bodyNodes);
      memberEntities.add(
          new ProcedureEntity(staticInitAst, typeBinding, childEntities, newContext, null));
    }

    Collection<CAstQualifier> quals =
        JDT2CAstUtils.mapModifiersToQualifiers(modifiers, isInterface, isAnnotation);

    if (n instanceof LambdaExpression) {
      return new ClassEntity(
          typeBinding, "L" + name, quals, memberEntities, makePosition(n), namePos) {
        final CAstType lt = fTypeDict.new JdtLambdaType(name, typeBinding);

        @Override
        public CAstType getType() {
          return lt;
        }
      };
    } else {
      return new ClassEntity(typeBinding, name, quals, memberEntities, makePosition(n), namePos);
    }
  }

  private CAstEntity visit(AnonymousClassDeclaration n, WalkContext context) {
    return createClassDeclaration(
        n,
        n.bodyDeclarations(),
        null,
        n.resolveBinding(),
        JDT2CAstUtils.anonTypeName(n.resolveBinding()),
        0 /* no modifiers */,
        false,
        false,
        context,
        null);
  }

  private CAstNode visit(TypeDeclarationStatement n, WalkContext context) {
    // TODO 1.6: enums of course...
    AbstractTypeDeclaration decl = n.getDeclaration();
    assert decl instanceof TypeDeclaration : "Local enum declaration not yet supported";
    CAstEntity classEntity = visitTypeDecl(decl, context);

    // these statements don't actually do anything, just define a type
    final CAstNode lcdNode = makeNode(context, fFactory, n, CAstNode.EMPTY);

    // so define it!
    context.addScopedEntity(lcdNode, classEntity);
    return lcdNode;
  }

  // ////////////////////////////////
  // METHODS
  // ////////////////////////////////

  /**
   * @param n for positioning.
   *     <p>Make a constructor with parameters that calls super(...) with parameters. Used for
   *     anonymous classes with arguments to a constructor, like new Foo(arg1,arg2) { }
   */
  private CAstEntity createDefaultConstructorWithParameters(
      IMethodBinding ctor, ASTNode n, WalkContext oldContext, ArrayList<ASTNode> inits) {
    // PART I: find super ctor to call
    ITypeBinding newType = ctor.getDeclaringClass();
    ITypeBinding superType = newType.getSuperclass();
    IMethodBinding superCtor = null;

    for (IMethodBinding m : superType.getDeclaredMethods())
      if (m.isConstructor() && Arrays.equals(m.getParameterTypes(), ctor.getParameterTypes()))
        superCtor = m;

    assert superCtor != null : "couldn't find constructor for anonymous class";

    // PART II: make ctor with simply "super(a,b,c...)"
    final Map<CAstNode, CAstEntity> memberEntities = new LinkedHashMap<>();
    final MethodContext context = new MethodContext(oldContext, memberEntities);
    MethodDeclaration fakeCtor = ast.newMethodDeclaration();
    fakeCtor.setConstructor(true);
    fakeCtor.setSourceRange(n.getStartPosition(), n.getLength());
    fakeCtor.setBody(ast.newBlock());

    // PART IIa: make a fake JDT constructor method with the proper number of args
    // Make fake args that will be passed
    String[] fakeArguments = new String[superCtor.getParameterTypes().length + 1];
    ArrayList<CAstType> paramTypes = new ArrayList<>(superCtor.getParameterTypes().length);
    // TODO: change to invalid name and don't use
    Arrays.setAll(fakeArguments, i -> (i == 0) ? "this" : ("argument" + i));
    // singlevariabledeclaration below
    for (int i = 1; i < fakeArguments.length; i++) {
      // the name
      SingleVariableDeclaration svd = ast.newSingleVariableDeclaration();
      svd.setName(ast.newSimpleName(fakeArguments[i]));
      fakeCtor.parameters().add(svd);

      // the type
      paramTypes.add(fTypeDict.getCAstTypeFor(ctor.getParameterTypes()[i - 1]));
    }

    // PART IIb: create the statements in the constructor
    // one super() call plus the inits
    List<CAstNode> bodyNodes = new ArrayList<>(inits.size() + 1);

    // make super(...) call
    // this, call ref, args
    List<CAstNode> children = new ArrayList<>(fakeArguments.length + 1);
    children.add(makeNode(context, fFactory, n, CAstNode.SUPER));
    CallSiteReference callSiteRef =
        CallSiteReference.make(
            0, fIdentityMapper.getMethodRef(superCtor), IInvokeInstruction.Dispatch.SPECIAL);
    children.add(fFactory.makeConstant(callSiteRef));
    for (int i = 1; i < fakeArguments.length; i++) {
      CAstNode argName = fFactory.makeConstant(fakeArguments[i]);
      CAstNode argType = fFactory.makeConstant(paramTypes.get(i - 1));
      children.add(makeNode(context, fFactory, n, CAstNode.VAR, argName, argType));
    }
    bodyNodes.add(makeNode(context, fFactory, n, CAstNode.CALL, children));
    // QUESTION: no handleExceptions?

    for (ASTNode init : inits) bodyNodes.add(visitFieldInitNode(init, context));

    // finally, make the procedure entity
    CAstNode ast = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, bodyNodes);
    return new ProcedureEntity(
        ast, fakeCtor, newType, memberEntities, context, paramTypes, null, null);
  }

  private CAstEntity createDefaultConstructor(
      ITypeBinding classBinding,
      WalkContext oldContext,
      ArrayList<ASTNode> inits,
      ASTNode positioningNode) {
    MethodDeclaration fakeCtor = ast.newMethodDeclaration();
    fakeCtor.setConstructor(true);
    // fakeCtor.setName(ast.newSimpleName(className)); will crash on anonymous types...
    fakeCtor.setSourceRange(positioningNode.getStartPosition(), positioningNode.getLength());
    fakeCtor.setBody(ast.newBlock());

    return visit(fakeCtor, classBinding, oldContext, inits);
  }

  private static IMethodBinding findDefaultCtor(ITypeBinding superClass) {
    for (IMethodBinding met : superClass.getDeclaredMethods()) {
      if (met.isConstructor() && met.getParameterTypes().length == 0) return met;
    }
    Assertions.UNREACHABLE("Couldn't find default ctor");
    return null;
  }

  /**
   * Setup constructor body. Here we add the initializer code (both initalizer blocks and
   * initializers in field declarations). We may also need to add an implicit super() call.
   *
   * @param classBinding Used so we can use this with fake MethodDeclaration nodes, as in the case
   *     of creating a default constructor.
   */
  private CAstNode createConstructorBody(
      MethodDeclaration n,
      ITypeBinding classBinding,
      WalkContext context,
      ArrayList<ASTNode> inits) {
    // three possibilites: has super(), has this(), has neither.

    Statement firstStatement = null;
    if (!n.getBody().statements().isEmpty())
      firstStatement = (Statement) n.getBody().statements().get(0);
    if (firstStatement instanceof SuperConstructorInvocation) {
      // Split at call to super:
      // super();
      // field initializer code
      // remainder of ctor body
      ArrayList<CAstNode> origStatements = createBlock(n.getBody(), context);
      List<CAstNode> bodyNodes = new ArrayList<>(inits.size() + origStatements.size());
      bodyNodes.add(origStatements.get(0));
      for (ASTNode init : inits)
        bodyNodes.add(
            visitFieldInitNode(
                init, context)); // visit each in this constructor's context, ensuring
      // proper handling of exceptions (we can't just reuse the
      // CAstNodes)
      for (int i = 1; i < origStatements.size(); i++) bodyNodes.add(origStatements.get(i));

      return makeNode(
          context,
          fFactory,
          n.getBody(),
          CAstNode.BLOCK_STMT,
          bodyNodes); // QUESTION: why no LOCAL_SCOPE?
      // that's the way it is in
      // polyglot.
    } else if (firstStatement instanceof ConstructorInvocation) {
      return visitNode(
          n.getBody(), context); // has this(...) call; initializers will be set somewhere else.
    } else {
      // add explicit call to default super()
      // QUESTION their todo: following superClass lookup of default ctor won't work if we
      // process Object in source...

      ITypeBinding superType = classBinding.getSuperclass();

      // find default constructor. IT is an error to have a constructor
      // without super() when the default constructor of the superclass does not exist.
      IMethodBinding defaultSuperCtor = findDefaultCtor(superType);
      CallSiteReference callSiteRef =
          CallSiteReference.make(
              0,
              fIdentityMapper.getMethodRef(defaultSuperCtor),
              IInvokeInstruction.Dispatch.SPECIAL);

      // QUESTION: why isn't first arg this like in visit(ConstructorInvocation) ?
      // why don't we handle exceptions like in visit(ConstructorInvocation) ? (these two things are
      // same in polyglot
      // implementation)

      CAstNode superCall =
          makeNode(
              context,
              fFactory,
              n.getBody(),
              CAstNode.CALL,
              makeNode(context, fFactory, n.getBody(), CAstNode.SUPER),
              fFactory.makeConstant(callSiteRef));
      Object mapper = new Object(); // dummy used for mapping this node in CFG
      handleThrowsFromCall(defaultSuperCtor, mapper, context);
      context.cfg().map(mapper, superCall);

      ArrayList<CAstNode> origStatements = createBlock(n.getBody(), context);
      List<CAstNode> bodyNodes = new ArrayList<>(inits.size() + origStatements.size() + 1);
      // superCall, inits, ctor body
      bodyNodes.add(superCall);
      for (ASTNode init : inits) bodyNodes.add(visitFieldInitNode(init, context));
      bodyNodes.addAll(origStatements);
      return makeNode(context, fFactory, n.getBody(), CAstNode.BLOCK_STMT, bodyNodes);
    }
  }

  /**
   * Make a "fake" function (it doesn't exist in source code but it does in bytecode) for covariant
   * return types.
   *
   * @param overriding Declaration of the overriding method.
   * @param overridden Binding of the overridden method, in a a superclass or implemented interface.
   */
  private CAstEntity makeSyntheticCovariantRedirect(
      MethodDeclaration overriding,
      IMethodBinding overridingBinding,
      IMethodBinding overridden,
      WalkContext oldContext) {
    // SuperClass foo(A, B, C...)
    // SubClass foo(A,B,C...)
    //
    // add a method exactly like overridden that calls overriding

    final Map<CAstNode, CAstEntity> memberEntities = new LinkedHashMap<>();
    final MethodContext context = new MethodContext(oldContext, memberEntities);

    CAstNode calltarget;
    if ((overridingBinding.getModifiers() & Modifier.STATIC) == 0)
      calltarget = makeNode(context, fFactory, null, CAstNode.SUPER);
    else calltarget = makeNode(context, fFactory, null, CAstNode.VOID);

    ITypeBinding paramTypes[] = overridden.getParameterTypes();

    ArrayList<CAstNode> arguments = new ArrayList<>();
    int i = 0;
    for (SingleVariableDeclaration svd :
        (Iterable<SingleVariableDeclaration>) overriding.parameters()) {
      CAstNode varNode =
          makeNode(
              context,
              fFactory,
              null,
              CAstNode.VAR,
              fFactory.makeConstant(svd.getName().getIdentifier()));
      ITypeBinding fromType = JDT2CAstUtils.getErasedType(paramTypes[i], ast);
      ITypeBinding toType =
          JDT2CAstUtils.getErasedType(overridingBinding.getParameterTypes()[i], ast);
      if (fromType.equals(toType)) {
        arguments.add(varNode);
      } else {
        arguments.add(createCast(null, varNode, fromType, toType, context));
      }
      i++;
    }
    CAstNode callnode =
        createMethodInvocation(null, overridingBinding, calltarget, arguments, context);
    CAstNode mdast =
        makeNode(
            context,
            fFactory,
            null,
            CAstNode.LOCAL_SCOPE,
            makeNode(
                context,
                fFactory,
                null,
                CAstNode.BLOCK_STMT,
                makeNode(context, fFactory, null, CAstNode.RETURN, callnode)));

    // make parameters to new synthetic method
    // use RETURN TYPE of overridden, everything else from overriding (including parameter names)
    ArrayList<CAstType> paramCAstTypes = new ArrayList<>(overridden.getParameterTypes().length);
    for (ITypeBinding paramType : overridden.getParameterTypes())
      paramCAstTypes.add(fTypeDict.getCAstTypeFor(paramType));
    return new ProcedureEntity(
        mdast,
        overriding,
        overridingBinding.getDeclaringClass(),
        memberEntities,
        context,
        paramCAstTypes,
        overridden.getReturnType(),
        null);
  }

  /**
   * @param inits Instance intializers & field initializers. Only used if method is a constructor,
   *     in which case the initializers will be inserted in.
   */
  private CAstEntity visit(
      MethodDeclaration n,
      ITypeBinding classBinding,
      WalkContext oldContext,
      ArrayList<ASTNode> inits) {

    // pass in memberEntities to the context, later visit(New) etc. may add classes
    final Map<CAstNode, CAstEntity> memberEntities = new LinkedHashMap<>();
    final MethodContext context =
        new MethodContext(oldContext, memberEntities); // LEFTOUT: in polyglot there is a
    // class context in between method and
    // root

    CAstNode mdast;

    if (n.isConstructor()) mdast = createConstructorBody(n, classBinding, context, inits);
    else if ((n.getModifiers() & Modifier.ABSTRACT) != 0) {
      // abstract
      mdast = null;
    } else if (n.getBody() == null || n.getBody().statements().isEmpty()) {
      // empty
      mdast = makeNode(context, fFactory, n, CAstNode.RETURN);
    } else mdast = visitNode(n.getBody(), context);
    // Polyglot comment: Presumably the MethodContext's parent is a ClassContext,
    // and he has the list of initializers. Hopefully the following
    // will glue that stuff in the right place in any constructor body.

    if (context.getNameDecls() != null && !context.getNameDecls().isEmpty()) {
      // new first statement will be a block declaring all names.
      mdast =
          fFactory.makeNode(
              CAstNode.BLOCK_STMT,
              context.getNameDecls().size() == 1
                  ? context.getNameDecls().iterator().next()
                  : fFactory.makeNode(CAstNode.BLOCK_STMT, context.getNameDecls()),
              mdast);
    }

    Set<CAstAnnotation> annotations = null;
    if (n.resolveBinding() != null) {
      annotations = handleAnnotations(n.resolveBinding());
    }

    return new ProcedureEntity(mdast, n, classBinding, memberEntities, context, annotations);
  }

  public CAstNode visit(LambdaExpression n, WalkContext oldContext) {

    ITypeBinding typeBinding = n.resolveTypeBinding().getErasure();
    List<?> parameters = n.parameters();
    List<CAstType> castTypes = new ArrayList<>();
    List<String> castNames = new ArrayList<>();

    IMethodBinding fm = typeBinding.getFunctionalInterfaceMethod();
    assert fm != null : typeBinding;
    ITypeBinding ct = fm.getDeclaringClass();

    for (int i = 0; i < parameters.size(); i++) {
      VariableDeclarationFragment tmp = (VariableDeclarationFragment) parameters.get(i);
      CAstType td = fTypeDict.getCAstTypeFor(fm.getParameterTypes()[i].getErasure());
      castTypes.add(td);
      String tmpName = tmp.getName().getIdentifier();
      castNames.add(tmpName);
    }

    final MethodContext context = new MethodContext(oldContext, Collections.emptyMap());

    CAstNode mdast;
    if (fm.getReturnType().isPrimitive() && fm.getReturnType().getName().equals("void")) {
      mdast = visitNode(n.getBody(), context);
    } else {
      mdast = fFactory.makeNode(CAstNode.RETURN, visitNode(n.getBody(), context));
    }

    if (context.getNameDecls() != null && !context.getNameDecls().isEmpty()) {
      // new first statement will be a block declaring all names.
      mdast =
          fFactory.makeNode(
              CAstNode.BLOCK_STMT,
              context.getNameDecls().size() == 1
                  ? context.getNameDecls().iterator().next()
                  : fFactory.makeNode(CAstNode.BLOCK_STMT, context.getNameDecls()),
              mdast);
    }

    CAstEntity lambdaClass =
        createClassDeclaration(
            n,
            Collections.emptyList(),
            null,
            ct,
            JDT2CAstUtils.anonTypeName(ct),
            0 /* no modifiers */,
            false,
            false,
            context,
            null);

    castNames.add(0, "this");
    castTypes.add(0, lambdaClass.getType());

    CAstEntity e =
        new ProcedureEntity(
            mdast,
            ct,
            Collections.emptyMap(),
            context,
            Collections.emptySet(),
            castTypes,
            castNames) {
          private final CAstType myType =
              new JdtMethodCAstType() {

                @Override
                public int getArgumentCount() {
                  return castTypes.size() - 1;
                }

                @Override
                public List<CAstType> getArgumentTypes() {
                  return castTypes.stream().skip(1).collect(Collectors.toList());
                }

                @Override
                public CAstType getDeclaringType() {
                  return lambdaClass.getType();
                }
              };

          @Override
          public CAstType getType() {
            return myType;
          }

          @Override
          public String getName() {
            return fm.getName();
          }
        };

    lambdaClass.getAllScopedEntities().get(null).add(e);
    TypeName tn = TypeName.string2TypeName(lambdaClass.getName());
    TypeReference tr = TypeReference.findOrCreate(fSourceLoader.getReference(), tn);

    /* Create a new node with only 1 child, the lambda class */
    CAstNode lambdaNode = fFactory.makeNode(CAstNode.NEW, fFactory.makeConstant(tr));
    oldContext.addScopedEntity(lambdaNode, lambdaClass);
    return lambdaNode;
  }

  private Set<CAstAnnotation> handleAnnotations(IBinding binding) {
    IAnnotationBinding[] annotations = binding.getAnnotations();

    if (annotations == null || annotations.length == 0) {
      return null;
    }

    Set<CAstAnnotation> castAnnotations = HashSetFactory.make();
    for (IAnnotationBinding annotation : annotations) {
      ITypeBinding annotationTypeBinding = annotation.getAnnotationType();
      final CAstType annotationType = fTypeDict.getCAstTypeFor(annotationTypeBinding);
      final Map<String, Object> args = HashMapFactory.make();
      for (IMemberValuePairBinding mvpb : annotation.getAllMemberValuePairs()) {
        String name = mvpb.getName();
        Object value = mvpb.getValue();
        args.put(name, value);
      }
      castAnnotations.add(
          new CAstAnnotation() {
            @Override
            public CAstType getType() {
              return annotationType;
            }

            @Override
            public Map<String, Object> getArguments() {
              return args;
            }

            @Override
            public String toString() {
              return annotationType.getName() + args;
            }
          });
    }

    return castAnnotations;
  }

  protected class ProcedureEntity
      implements JavaProcedureEntity { // TAGALONG (make static, access ast)

    protected class JdtMethodCAstType implements CAstType.Method {
      private Collection<CAstType> fExceptionTypes = null;

      @Override
      public boolean isStatic() {
        return getQualifiers().contains(CAstQualifier.STATIC);
      }

      @Override
      public CAstType getReturnType() {
        if (fReturnType != null) return fTypeDict.getCAstTypeFor(fReturnType);
        @SuppressWarnings("deprecation")
        Type type =
            fDecl == null
                ? null
                : (ast.apiLevel() == 2 ? fDecl.getReturnType() : fDecl.getReturnType2());
        if (type == null) return fTypeDict.getCAstTypeFor(ast.resolveWellKnownType("void"));
        else return fTypeDict.getCAstTypeFor(type.resolveBinding());
      }

      /** NOT INCLUDING first parameter 'this' (for non-static methods) */
      @Override
      public List<CAstType> getArgumentTypes() {
        return fParameterTypes;
      }

      /** NOT INCLUDING first parameter 'this' (for non-static methods) */
      @Override
      public int getArgumentCount() {
        return fDecl == null
            ? fParameterTypes != null ? fParameterTypes.size() : 0
            : fParameterTypes.size();
      }

      @Override
      public String getName() {
        throw new UnsupportedOperationException("CAstType.FunctionImpl#getName() called???");
      }

      @Override
      public Collection<CAstType> getSupertypes() {
        throw new UnsupportedOperationException("CAstType.FunctionImpl#getSupertypes() called???");
      }

      @Override
      public Collection<CAstType> /* <CAstType> */ getExceptionTypes() {
        if (fExceptionTypes == null) {
          fExceptionTypes = new LinkedHashSet<>();
          if (fDecl != null)
            for (SimpleType exception : (Iterable<SimpleType>) fDecl.thrownExceptionTypes())
              fExceptionTypes.add(fTypeDict.getCAstTypeFor(exception.resolveBinding()));
        }
        return fExceptionTypes;
      }

      @Override
      public CAstType getDeclaringType() {
        return fTypeDict.getCAstTypeFor(fType);
      }
    }

    // From Code Body Entity
    private final Map<CAstNode, Collection<CAstEntity>> fEntities;

    @Override
    public Map<CAstNode, Collection<CAstEntity>> getAllScopedEntities() {
      return Collections.unmodifiableMap(fEntities);
    }

    @Override
    public Iterator<CAstEntity> getScopedEntities(CAstNode construct) {
      if (fEntities.containsKey(construct)) {
        return fEntities.get(construct).iterator();
      } else {
        return EmptyIterator.instance();
      }
    }

    @Override
    public String getSignature() {
      return Util.methodEntityToSelector(this).toString();
    }

    private final CAstNode fAst;

    MethodDeclaration fDecl; // TAGALONG serious tagalong...

    private final String[] fParameterNames; // INCLUDING this

    private final List<CAstType> fParameterTypes;

    private final MethodContext fContext; // possibly TAGALONG, maybe not

    private final ITypeBinding fType; // TAGALONG

    private final ITypeBinding fReturnType;

    private final int fModifiers;

    private final Set<CAstAnnotation> annotations;

    // can be method, constructor, "fake" default constructor, or null decl = static initializer
    /** For a static initializer, pass a null decl. */
    // FIXME: get rid of decl and pass in everything instead of having to do two different things
    // with parameters
    // regular case
    private ProcedureEntity(
        CAstNode mdast,
        MethodDeclaration decl,
        ITypeBinding type,
        Map<CAstNode, CAstEntity> entities,
        MethodContext context,
        Set<CAstAnnotation> annotations) {
      this(
          mdast, decl, type, entities, context, null, null, decl.getModifiers(), annotations, null);
    }

    // static init
    private ProcedureEntity(
        CAstNode mdast,
        ITypeBinding type,
        Map<CAstNode, CAstEntity> entities,
        MethodContext context,
        Set<CAstAnnotation> annotations) {
      this(mdast, null, type, entities, context, null, null, 0, annotations, null);
    }

    // Constructor with appropriate arguments for lambda expression types
    private ProcedureEntity(
        CAstNode mdast,
        ITypeBinding type,
        Map<CAstNode, CAstEntity> entities,
        MethodContext context,
        ArrayList<CAstType> parameterTypes,
        Set<CAstAnnotation> annotations) {
      this(mdast, null, type, entities, context, parameterTypes, null, 0, annotations, null);
    }

    private ProcedureEntity(
        CAstNode mdast,
        MethodDeclaration decl,
        ITypeBinding type,
        Map<CAstNode, CAstEntity> entities,
        MethodContext context,
        ArrayList<CAstType> parameterTypes,
        ITypeBinding returnType,
        Set<CAstAnnotation> annotations) {
      this(
          mdast,
          decl,
          type,
          entities,
          context,
          parameterTypes,
          returnType,
          decl.getModifiers(),
          annotations,
          null);
    }

    private ProcedureEntity(
        CAstNode mdast,
        MethodDeclaration decl,
        ITypeBinding type,
        Map<CAstNode, CAstEntity> entities,
        MethodContext context,
        List<CAstType> parameterTypes,
        ITypeBinding returnType,
        int modifiers,
        Set<CAstAnnotation> annotations,
        List<String> parameterNames) {
      // TypeSystem system, CodeInstance pd, String[] argumentNames,
      // }
      // Map<CAstNode, CAstEntity> entities, MethodContext mc) {
      fDecl = decl;
      fAst = mdast; // "procedure decl ast"
      fContext = context;
      fType = type;
      fReturnType = returnType;
      fModifiers = modifiers;
      this.annotations = annotations;

      // from CodeBodyEntity
      fEntities = new LinkedHashMap<>();
      for (Map.Entry<CAstNode, CAstEntity> entry : entities.entrySet()) {
        fEntities.put(entry.getKey(), Collections.singleton(entry.getValue()));
      }

      if (fDecl != null) {
        int i; // index to start filling up with real params
        if ((fModifiers & Modifier.STATIC) != 0) {
          fParameterNames = new String[fDecl.parameters().size()];
          i = 0;
        } else {
          fParameterNames = new String[fDecl.parameters().size() + 1];
          fParameterNames[0] = "this";
          i = 1;
        }

        if (parameterTypes == null) {
          fParameterTypes = new ArrayList<>(fDecl.parameters().size());
          for (SingleVariableDeclaration p :
              (Iterable<SingleVariableDeclaration>) fDecl.parameters()) {
            fParameterNames[i++] = p.getName().getIdentifier();
            fParameterTypes.add(fTypeDict.getCAstTypeFor(p.resolveBinding().getType()));
          }
        } else {
          // currently this is only used in making a default constructor with arguments (anonymous
          // classes).
          // this is because we cannot synthesize bindings.
          fParameterTypes = parameterTypes;
          for (SingleVariableDeclaration p :
              (Iterable<SingleVariableDeclaration>) fDecl.parameters()) {
            fParameterNames[i++] = p.getName().getIdentifier();
          }
        }
      } else if (parameterNames != null) {
        fParameterNames = parameterNames.toArray(new String[0]);
        fParameterTypes = parameterTypes;
      } else {
        fParameterNames = new String[0];
        fParameterTypes = new ArrayList<>(0); // static initializer
      }
    }

    // TODO: Complete this constructor declaration.
    public ProcedureEntity(
        CAstNode mdast,
        ITypeBinding typeBinding,
        Map<CAstNode, CAstEntity> emptyMap,
        JDTJava2CAstTranslator<T>.MethodContext context,
        Set<CAstAnnotation> emptySet,
        List<CAstType> castTypes,
        List<String> castNames) {
      this(
          mdast,
          null,
          typeBinding,
          emptyMap,
          context,
          castTypes,
          typeBinding.getFunctionalInterfaceMethod().getReturnType(),
          0,
          emptySet,
          castNames);
    }

    @Override
    public Collection<CAstAnnotation> getAnnotations() {
      return annotations;
    }

    @Override
    public String toString() {
      return fDecl == null ? getName() : fDecl.toString();
    }

    @Override
    public int getKind() {
      return CAstEntity.FUNCTION_ENTITY;
    }

    @Override
    public String getName() {
      if (fDecl == null) return MethodReference.clinitName.toString();
      else if (fDecl.isConstructor()) return MethodReference.initAtom.toString();
      else return fDecl.getName().getIdentifier();
    }

    /** INCLUDING first parameter 'this' (for non-static methods) */
    @Override
    public String[] getArgumentNames() {
      return fParameterNames;
    }

    @Override
    public CAstNode[] getArgumentDefaults() {
      return new CAstNode[0];
    }

    /** INCLUDING first parameter 'this' (for non-static methods) */
    @Override
    public int getArgumentCount() {
      return fParameterNames.length;
    }

    @Override
    public CAstNode getAST() {
      return fAst;
    }

    @Override
    public CAstControlFlowMap getControlFlow() {
      return fContext.cfg();
    }

    @Override
    public CAstSourcePositionMap getSourceMap() {
      return fContext.pos();
    }

    @Override
    public CAstSourcePositionMap.Position getPosition() {
      return fDecl == null ? getSourceMap().getPosition(fAst) : makePosition(fDecl);
    }

    @Override
    public CAstNodeTypeMap getNodeTypeMap() {
      return fContext.getNodeTypeMap();
    }

    @Override
    public Collection<CAstQualifier> getQualifiers() {
      if (fDecl == null && (fParameterTypes == null || fParameterTypes.isEmpty()))
        return JDT2CAstUtils.mapModifiersToQualifiers(Modifier.STATIC, false, false); // static init
      else return JDT2CAstUtils.mapModifiersToQualifiers(fModifiers, false, false);
    }

    @Override
    public CAstType getType() {
      return new JdtMethodCAstType();
    }

    @Override
    public Position getPosition(int arg) {
      if (fDecl == null) {
        return null;
      } else {
        SingleVariableDeclaration p = (SingleVariableDeclaration) fDecl.parameters().get(arg);
        return makePosition(p);
      }
    }

    @Override
    public Position getNamePosition() {
      if (fDecl == null || fDecl.getName() == null) {
        return null;
      } else {
        return makePosition(fDecl.getName());
      }
    }
  }

  // ////////////////////////////////////
  // FIELDS ////////////////////////////
  // ////////////////////////////////////

  // FIELDS ADDED DIRECTLY IN visit(TypeDeclaration,WalkContext)

  private CAstNode visitFieldInitNode(ASTNode node, WalkContext context) {
    // there are gathered by createClassDeclaration and can only be of two types:
    if (node instanceof Initializer) {
      return visitNode(((Initializer) node).getBody(), context);
    } else if (node instanceof VariableDeclarationFragment) {
      VariableDeclarationFragment f =
          (VariableDeclarationFragment) node; // this is guaranteed to have an initializer

      // Generate CAST node for the initializer (init())
      // Type targetType = f.memberInstance().container();
      // Type fieldType = f.type().type();
      FieldReference fieldRef = fIdentityMapper.getFieldRef(f.resolveBinding());
      // We use null to indicate an OBJECT_REF to a static field, as the
      // FieldReference doesn't
      // hold enough info to determine this. In this case, (unlike field ref)
      // we don't have a
      // target expr to evaluate.
      boolean isStatic = ((f.resolveBinding().getModifiers() & Modifier.STATIC) != 0);
      CAstNode thisNode =
          isStatic
              ? makeNode(context, fFactory, null, CAstNode.VOID)
              : makeNode(context, fFactory, f, CAstNode.THIS);
      CAstNode lhsNode =
          makeNode(
              context, fFactory, f, CAstNode.OBJECT_REF, thisNode, fFactory.makeConstant(fieldRef));

      Expression init = f.getInitializer();
      CAstNode rhsNode = visitNode(init, context);
      return makeNode(context, fFactory, f, CAstNode.ASSIGN, lhsNode, rhsNode);

    } else if (node instanceof EnumConstantDeclaration) {
      return createEnumConstantDeclarationInit((EnumConstantDeclaration) node, context);
    } else {
      Assertions.UNREACHABLE("invalid init node gathered by createClassDeclaration");
      return null;
    }
  }

  protected final class FieldEntity implements CAstEntity {
    private final ITypeBinding type;

    private final String name;

    private final Collection<CAstQualifier> quals;

    private final T position;

    private final T namePos;

    private final Set<CAstAnnotation> annotations;

    private FieldEntity(
        String name,
        ITypeBinding type,
        Collection<CAstQualifier> quals,
        T position,
        Set<CAstAnnotation> annotations,
        T namePos) {
      this.type = type;
      this.quals = quals;
      this.name = name;
      this.namePos = namePos;
      this.position = position;
      this.annotations = annotations;
    }

    @Override
    public Collection<CAstAnnotation> getAnnotations() {
      return annotations;
    }

    @Override
    public int getKind() {
      return CAstEntity.FIELD_ENTITY;
    }

    @Override
    public String getName() {
      return name;
    }

    @Override
    public String getSignature() {
      return name + fIdentityMapper.typeToTypeID(type);
    }

    @Override
    public String[] getArgumentNames() {
      return new String[0];
    }

    @Override
    public CAstNode[] getArgumentDefaults() {
      return new CAstNode[0];
    }

    @Override
    public int getArgumentCount() {
      return 0;
    }

    @Override
    public Iterator<CAstEntity> getScopedEntities(CAstNode construct) {
      return EmptyIterator.instance();
    }

    @Override
    public Map<CAstNode, Collection<CAstEntity>> getAllScopedEntities() {
      return Collections.emptyMap();
    }

    @Override
    public CAstNode getAST() {
      // No AST for a field decl; initializers folded into
      // constructor processing...
      return null;
    }

    @Override
    public CAstControlFlowMap getControlFlow() {
      // No AST for a field decl; initializers folded into
      // constructor processing...
      return null;
    }

    @Override
    public CAstSourcePositionMap getSourceMap() {
      // No AST for a field decl; initializers folded into
      // constructor processing...
      return null;
    }

    @Override
    public CAstSourcePositionMap.Position getPosition() {
      return position;
    }

    @Override
    public CAstNodeTypeMap getNodeTypeMap() {
      // No AST for a field decl; initializers folded into
      // constructor processing...
      return null;
    }

    @Override
    public Collection<CAstQualifier> getQualifiers() {
      return quals;
    }

    @Override
    public CAstType getType() {
      return fTypeDict.getCAstTypeFor(type);
    }

    @Override
    public Position getPosition(int arg) {
      return namePos;
    }

    @Override
    public Position getNamePosition() {
      // TODO Auto-generated method stub
      return null;
    }
  }

  // /////////////////////////////////////
  // / NODES /////////////////////////////
  // /////////////////////////////////////

  /**
   * Visit all the statements in the block and return an arraylist of the statements. Some
   * statements (VariableDeclarationStatements) may expand to more than one CAstNode.
   */
  private ArrayList<CAstNode> createBlock(Block n, WalkContext context) {
    ArrayList<CAstNode> stmtNodes = new ArrayList<>();
    for (ASTNode s : (Iterable<ASTNode>) n.statements()) visitNodeOrNodes(s, context, stmtNodes);
    return stmtNodes;
  }

  private CAstNode visit(Block n, WalkContext context) {
    ArrayList<CAstNode> stmtNodes = createBlock(n, context);
    return makeNode(
        context,
        fFactory,
        n,
        CAstNode.LOCAL_SCOPE,
        makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, stmtNodes));
  }

  private CAstNode visit(VariableDeclarationFragment n, WalkContext context) {
    int modifiers;
    if (n.getParent() instanceof VariableDeclarationStatement)
      modifiers = ((VariableDeclarationStatement) n.getParent()).getModifiers();
    else if (n.getParent() instanceof VariableDeclarationExpression)
      modifiers = ((VariableDeclarationExpression) n.getParent()).getModifiers();
    else modifiers = ((FieldDeclaration) n.getParent()).getModifiers();
    boolean isFinal = (modifiers & Modifier.FINAL) != 0;
    assert n.resolveBinding() != null : n;
    ITypeBinding type = n.resolveBinding().getType();
    Expression init = n.getInitializer();
    CAstNode initNode;

    String t = type.getBinaryName();
    if (init == null) {
      if (JDT2CAstUtils.isLongOrLess(type)) {
        // doesn't include boolean
        initNode = fFactory.makeConstant(0);
      } else if (t.equals("D") || t.equals("F")) initNode = fFactory.makeConstant(0.0);
      else initNode = fFactory.makeConstant(null);
    } else initNode = visitNode(init, context);

    Object defaultValue = JDT2CAstUtils.defaultValueForType(type);
    return makeNode(
        context,
        fFactory,
        n,
        CAstNode.DECL_STMT,
        fFactory.makeConstant(
            new CAstSymbolImpl(
                n.getName().getIdentifier(),
                fTypeDict.getCAstTypeFor(type),
                isFinal,
                defaultValue)),
        initNode);
  }

  /*
   * One VariableDeclarationStatement represents more than one CAstNode statement.
   */
  private ArrayList<CAstNode> visit(VariableDeclarationStatement n, WalkContext context) {
    ArrayList<CAstNode> result = new ArrayList<>();

    for (VariableDeclarationFragment o : (Iterable<VariableDeclarationFragment>) n.fragments())
      result.add(visit(o, context));
    return result;
  }

  private CAstNode visit(VariableDeclarationExpression n, WalkContext context) {
    ArrayList<CAstNode> result = new ArrayList<>();

    for (VariableDeclarationFragment o : (Iterable<VariableDeclarationFragment>) n.fragments())
      result.add(visit(o, context));
    return fFactory.makeNode(CAstNode.BLOCK_EXPR, result);
  }

  private CAstNode visit(ArrayInitializer n, WalkContext context) {
    ITypeBinding type = n.resolveTypeBinding();
    assert type != null : "Could not determine type of ArrayInitializer";
    TypeReference newTypeRef = fIdentityMapper.getTypeRef(type);
    List<CAstNode> eltNodes = new ArrayList<>(n.expressions().size() + 1);
    eltNodes.add(
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.NEW,
            fFactory.makeConstant(newTypeRef),
            fFactory.makeConstant(n.expressions().size())));
    for (Expression element : (Iterable<Expression>) n.expressions()) {
      final CAstNode visited = visitNode(element, context);
      assert visited != null : element.toString();
      eltNodes.add(visited);
    }

    return makeNode(context, fFactory, n, CAstNode.ARRAY_LITERAL, eltNodes);
  }

  private CAstNode visit(ClassInstanceCreation n, WalkContext context) {
    return createClassInstanceCreation(
        n,
        n.arguments(),
        n.resolveConstructorBinding(),
        n.getExpression(),
        n.getAnonymousClassDeclaration(),
        context);
  }

  private CAstNode createClassInstanceCreation(
      ASTNode nn,
      List<?> arguments,
      IMethodBinding ctorBinding,
      Expression qual,
      AnonymousClassDeclaration anonDecl,
      WalkContext context) {
    // a new instruction is actually two things: a NEW object and a CALL to a constructor
    CAstNode newNode;
    CAstNode callNode;
    final String tmpName = "ctor temp";
    // this name is an illegal Java identifier. this var will hold the new object so we can call the
    // constructor on it

    // GENERICS getMethodDeclaration()
    ctorBinding = ctorBinding.getMethodDeclaration(); // unlike polyglot, this will
    // point to a default
    // constructor in the anon class
    MethodReference ctorRef = fIdentityMapper.getMethodRef(ctorBinding);

    // ////////////// PART I: make the NEW expression
    // ///////////////////////////////////////////////////////

    ITypeBinding newType = ctorBinding.getDeclaringClass();
    TypeReference newTypeRef = fIdentityMapper.getTypeRef(newType);

    // new nodes with an explicit enclosing argument, e.g. "outer.new Inner()". They are mostly
    // treated the same, except
    // in JavaCAst2IRTranslator.doNewObject
    CAstNode qualNode = null;

    if (qual == null
        && newType.getDeclaringClass() != null
        && ((newType.getModifiers() & Modifier.STATIC) == 0)
        && !newType.isLocal()) {
      // "new X()" expanded into "new this.X()" or "new MyClass.this.X"
      // check isLocal because anonymous classes and local classes are not included.
      ITypeBinding plainThisType = JDT2CAstUtils.getDeclaringClassOfNode(nn); // type of "this"
      ITypeBinding implicitThisType =
          findClosestEnclosingClassSubclassOf(
              plainThisType,
              newType.getDeclaringClass(),
              ((newType.getModifiers() & Modifier.PRIVATE) != 0));
      if (implicitThisType.isEqualTo(plainThisType))
        qualNode = makeNode(context, fFactory, nn, CAstNode.THIS); // "new this.X()"
      else
        qualNode =
            makeNode(
                context,
                fFactory,
                nn,
                CAstNode.THIS,
                fFactory.makeConstant(
                    fIdentityMapper.getTypeRef(implicitThisType))); // "new Bla.this.X()"
    } else if (qual != null) qualNode = visitNode(qual, context);

    if (qualNode != null)
      newNode =
          makeNode(
              context,
              fFactory,
              nn,
              CAstNode.NEW_ENCLOSING,
              fFactory.makeConstant(newTypeRef),
              qualNode);
    else newNode = makeNode(context, fFactory, nn, CAstNode.NEW, fFactory.makeConstant(newTypeRef));

    ITypeBinding[] newExceptions =
        new ITypeBinding[] {NoClassDefFoundError, ExceptionInInitializerError, OutOfMemoryError};
    context.cfg().map(newNode, newNode);
    for (ITypeBinding exp : newExceptions) {
      for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(exp)) {
        context.cfg().add(newNode, catchTarget.snd, catchTarget.fst);
      }
    }

    // ANONYMOUS CLASSES
    // ctor already points to right place, so should type ref, so all we have to do is make the
    // entity
    if (anonDecl != null) context.addScopedEntity(newNode, visit(anonDecl, context));

    // ////////////// PART II: make the CALL expression
    // ///////////////////////////////////////////////////////
    // setup args & handle exceptions

    List<CAstNode> argNodes = new ArrayList<>(arguments.size() + 2);

    // arg 0: this
    argNodes.add(
        makeNode(
            context,
            fFactory,
            nn,
            CAstNode.VAR,
            fFactory.makeConstant(tmpName),
            fFactory.makeConstant(fTypeDict.getCAstTypeFor(newType))));
    // contains output from newNode (see part III)

    // arg 1: call site ref (WHY?)
    int dummyPC =
        0; // Just want to wrap the kind of call; the "rear end" won't care about anything else
    CallSiteReference callSiteRef =
        CallSiteReference.make(dummyPC, ctorRef, IInvokeInstruction.Dispatch.SPECIAL);
    argNodes.add(fFactory.makeConstant(callSiteRef));

    // rest of args
    for (Object arg : arguments) {
      argNodes.add(
          (arg instanceof CAstNode) ? ((CAstNode) arg) : visitNode((Expression) arg, context));
    }
    callNode = makeNode(context, fFactory, nn, CAstNode.CALL, argNodes);
    context.cfg().map(nn, callNode);

    handleThrowsFromCall(ctorBinding, nn, context);

    // PART III: make a node with both NEW and CALL
    // Make a LOCAL_SCOPE with a BLOCK_EXPR node child which does three things:
    // 1) declare a temporary variable and assign the new object to it (LOCAL_SCOPE is needed to
    // chain this new variable
    // to this block)
    // 2) CALL the constructor on the new variable
    // 3) access this temporary variable. Since the value of the block is the last thing in the
    // block, the resultant
    // value will be the variable
    return makeNode(
        context,
        fFactory,
        nn,
        CAstNode.LOCAL_SCOPE,
        makeNode(
            context,
            fFactory,
            nn,
            CAstNode.BLOCK_EXPR,
            makeNode(
                context,
                fFactory,
                nn,
                CAstNode.DECL_STMT,
                fFactory.makeConstant(
                    new InternalCAstSymbol(tmpName, fTypeDict.getCAstTypeFor(newType), true)),
                newNode),
            callNode,
            makeNode(context, fFactory, nn, CAstNode.VAR, fFactory.makeConstant(tmpName))));
  }

  /**
   * @param mappedAstNode An AST node or object mapped in the CFG: we will call context.cfg().add()
   *     on it. Caller must worry about mapping it with context.cfg().map().
   */
  private void handleThrowsFromCall(IMethodBinding met, Object mappedAstNode, WalkContext context) {
    ITypeBinding[] throwTypes = met.getExceptionTypes();
    for (ITypeBinding thrownType : throwTypes) {
      Collection<Pair<ITypeBinding, Object>> catchTargets = context.getCatchTargets(thrownType);
      for (Pair<ITypeBinding, Object> catchTarget : catchTargets)
        context.cfg().add(mappedAstNode, catchTarget.snd, catchTarget.fst);
    }
    // can also throw runtime exception
    for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(fRuntimeExcType))
      context.cfg().add(mappedAstNode, catchTarget.snd, catchTarget.fst);
  }

  private CAstNode visit(ExpressionStatement n, WalkContext context) {
    return visitNode(n.getExpression(), context);
  }

  private CAstNode visit(SuperMethodInvocation n, WalkContext context) {
    CAstNode target;
    if (n.getQualifier() == null) target = makeNode(context, fFactory, n, CAstNode.SUPER);
    else {
      TypeReference owningTypeRef =
          fIdentityMapper.getTypeRef(n.getQualifier().resolveTypeBinding());
      target = makeNode(context, fFactory, n, CAstNode.SUPER, fFactory.makeConstant(owningTypeRef));
    }
    // GENERICS getMethodDeclaration()
    return createMethodInvocation(
        n, n.resolveMethodBinding().getMethodDeclaration(), target, n.arguments(), context);
  }

  // FIXME: implicit this
  private CAstNode visit(MethodInvocation n, WalkContext context) {
    // GENERICS getMethodDeclaration()
    IMethodBinding binding = n.resolveMethodBinding().getMethodDeclaration();
    if ((binding.getModifiers() & Modifier.STATIC) != 0) {
      CAstNode target;

      // JLS says: evaluate qualifier & throw away unless of course it's just a class name (or
      // null),
      // in which case we replace the EMPTY with a VOID
      // of course, "this" has no side effects either.
      target = visitNode(n.getExpression(), context);
      if (target.getKind() == CAstNode.EMPTY || target.getKind() == CAstNode.THIS)
        return createMethodInvocation(
            n, binding, makeNode(context, fFactory, null, CAstNode.VOID), n.arguments(), context);
      else
        return makeNode(
            context,
            fFactory,
            n,
            CAstNode.BLOCK_EXPR,
            target,
            createMethodInvocation(
                n,
                binding,
                makeNode(context, fFactory, null, CAstNode.VOID),
                n.arguments(),
                context));
      // target is evaluated but thrown away, and only result of method invocation is kept

    } else {
      CAstNode target;
      if (n.getExpression() != null) {
        target = visitNode(n.getExpression(), context);
      } else {
        ITypeBinding typeOfThis = JDT2CAstUtils.getDeclaringClassOfNode(n);

        boolean methodIsPrivate = (binding.getModifiers() & Modifier.PRIVATE) != 0;

        // how could it be in the subtype and private? this only happens the supertype is also an
        // enclosing type. in that case the variable refers to the field in the enclosing instance.
        // NOTE: method may be defined in MyClass's superclass, but we still want to expand
        // this into MyClass, so we have to find the enclosing class which defines this function.

        ITypeBinding implicitThisClass =
            findClosestEnclosingClassSubclassOf(
                typeOfThis, binding.getDeclaringClass(), methodIsPrivate);
        if (typeOfThis.isEqualTo(implicitThisClass))
          // "foo = 5" -> "this.foo = 5": expand into THIS + class
          target = makeNode(context, fFactory, n, CAstNode.THIS);
        else
          // "foo = 5" -> "MyClass.this.foo = 5" -- inner class: expand into THIS + class
          target =
              makeNode(
                  context,
                  fFactory,
                  n,
                  CAstNode.THIS,
                  fFactory.makeConstant(fIdentityMapper.getTypeRef(implicitThisClass)));
        // NOTE: method may be defined in MyClass's superclass, but we still want to expand
        // this into MyClass, so we have to find the enclosing class which defines this function.
      }
      CAstNode node = createMethodInvocation(n, binding, target, n.arguments(), context);
      // TODO: maybe not exactly right... what if it's a capture? we may have to cast it down a
      // little bit.
      if (binding.getReturnType().isTypeVariable()) {
        // GENERICS: add a cast
        ITypeBinding realtype =
            JDT2CAstUtils.getErasedType(n.resolveMethodBinding().getReturnType(), ast);
        ITypeBinding fromtype = JDT2CAstUtils.getTypesVariablesBase(binding.getReturnType(), ast);
        if (!realtype.isEqualTo(fromtype)) return createCast(n, node, fromtype, realtype, context);
      }
      return node;
    }
  }

  private CAstNode createMethodInvocation(
      ASTNode pos,
      IMethodBinding methodBinding,
      CAstNode target,
      List<?> arguments,
      WalkContext context) {
    // MethodMethodInstance methodInstance = n.methodInstance();
    boolean isStatic = (methodBinding.getModifiers() & Modifier.STATIC) != 0;
    ITypeBinding methodOwner = methodBinding.getDeclaringClass();

    if (!(methodOwner.isInterface() || methodOwner.isClass() || methodOwner.isEnum())) {
      assert false : "owner " + methodOwner + " of " + methodBinding + " is not a class";
    }

    // POPULATE PARAMETERS
    // this (or void for static), method reference, rest of args
    int nFormals = methodBinding.getParameterTypes().length;
    List<CAstNode> children = new ArrayList<>(2 + nFormals);

    // this (or void for static)
    assert target != null : "no receiver for " + methodBinding;
    children.add(target);

    // method reference
    // unlike polyglot, expression will never be super here. this is handled in
    // SuperMethodInvocation.
    IInvokeInstruction.IDispatch dispatchType;
    if (isStatic) dispatchType = IInvokeInstruction.Dispatch.STATIC;
    else if (methodOwner.isInterface()) dispatchType = IInvokeInstruction.Dispatch.INTERFACE;
    else if ((methodBinding.getModifiers() & Modifier.PRIVATE) != 0
        || target.getKind() == CAstNode.SUPER)
      // only one possibility, not a virtual call (I guess?)
      dispatchType = IInvokeInstruction.Dispatch.SPECIAL;
    else dispatchType = IInvokeInstruction.Dispatch.VIRTUAL;
    // pass 0 for dummyPC: Just want to wrap the kind of call; the "rear end" won't care about
    // anything else...
    CallSiteReference callSiteRef =
        CallSiteReference.make(0, fIdentityMapper.getMethodRef(methodBinding), dispatchType);

    children.add(fFactory.makeConstant(callSiteRef));

    populateArguments(children, methodBinding, arguments, context);

    Object fakeCfgMap = new Object();

    handleThrowsFromCall(methodBinding, fakeCfgMap, context);

    CAstNode result = makeNode(context, fFactory, pos, CAstNode.CALL, children);
    context.cfg().map(fakeCfgMap, result);
    return result;
  }

  /**
   * Populate children, starting at index 2, for the invocation of methodBinding. If varargs are
   * used this function will collapse the proper arguments into an array.
   *
   * <p>If the number of actuals equals the number of formals and the function is varargs, we have
   * to check the type of the last argument to see if we should "box" it in an array. If the
   * arguments[arguments.length-1] is not an Expression, we cannot get the type, so we do not box
   * it. (Making covariant varargs functions require this behavior)
   */
  private void populateArguments(
      List<CAstNode> children,
      IMethodBinding methodBinding,
      List<? /* CAstNode or Expression */> arguments,
      WalkContext context) {
    int nFormals = methodBinding.getParameterTypes().length;
    assert children.size() == 2;
    int nActuals = arguments.size();

    ITypeBinding lastArgType = null;
    if (nActuals > 0 && arguments.get(nActuals - 1) instanceof Expression)
      lastArgType = ((Expression) arguments.get(nActuals - 1)).resolveTypeBinding();
    // if the # of actuals equals the # of formals, AND the function is varargs, we have to check
    // to see if the lastArgType is subtype compatible with the type of last parameter (which will
    // be an array).
    // If it is, we pass this array in directly. Otherwise this it is wrapped in an array init.
    // Example: 'void foo(int... x)' can be run via 'foo(5)' or 'foo(new int[] { 5, 6 })' -- both
    // have one argument so we must check
    // the type

    if (nActuals == nFormals
        && (!methodBinding.isVarargs()
            || lastArgType == null
            || lastArgType.isSubTypeCompatible(methodBinding.getParameterTypes()[nFormals - 1]))) {
      for (Object arg : arguments)
        children.add(
            (arg instanceof CAstNode) ? ((CAstNode) arg) : visitNode((Expression) arg, context));
    } else {
      assert nActuals >= (nFormals - 1) && methodBinding.isVarargs()
          : "Invalid number of parameters for constructor call";
      for (int i = 0; i < nFormals - 1; i++) {
        Object arg = arguments.get(i);
        children.add(
            (arg instanceof CAstNode) ? ((CAstNode) arg) : visitNode((Expression) arg, context));
      }

      final int numSubargs = nActuals - nFormals + 2;
      List<CAstNode> subargs = new ArrayList<>(numSubargs);
      // nodes for args and one extra for NEW expression
      TypeReference newTypeRef =
          fIdentityMapper.getTypeRef(methodBinding.getParameterTypes()[nFormals - 1]);
      subargs.add(
          makeNode(
              context,
              fFactory,
              null,
              CAstNode.NEW,
              fFactory.makeConstant(newTypeRef),
              fFactory.makeConstant(numSubargs - 1)));
      for (int j = 1; j < numSubargs; j++) {
        Object arg = arguments.get(j + nFormals - 2);
        subargs.add(
            (arg instanceof CAstNode) ? ((CAstNode) arg) : visitNode((Expression) arg, context));
      }
      children.add(makeNode(context, fFactory, (ASTNode) null, CAstNode.ARRAY_LITERAL, subargs));
    }
  }

  private CAstNode visit(ReturnStatement r, WalkContext context) {
    Expression retExpr = r.getExpression();
    if (retExpr == null) return makeNode(context, fFactory, r, CAstNode.RETURN);
    else return makeNode(context, fFactory, r, CAstNode.RETURN, visitNode(retExpr, context));
  }

  private CAstNode visit(Assignment n, WalkContext context) {
    if (n.getOperator() == Assignment.Operator.ASSIGN)
      return makeNode(
          context,
          fFactory,
          n,
          CAstNode.ASSIGN,
          visitNode(n.getLeftHandSide(), new AssignmentContext(context)),
          visitNode(n.getRightHandSide(), context));
    else {
      CAstNode left = visitNode(n.getLeftHandSide(), context);
      // GENERICs lvalue for pre op hack
      if (left.getKind() == CAstNode.CAST) {
        return doFunkyGenericAssignPreOpHack(n, context);
      }

      // +=, %=, &=, etc.
      CAstNode result =
          makeNode(
              context,
              fFactory,
              n,
              CAstNode.ASSIGN_PRE_OP,
              left,
              visitNode(n.getRightHandSide(), context),
              JDT2CAstUtils.mapAssignOperator(n.getOperator()));

      // integer division by zero
      if (JDT2CAstUtils.isLongOrLess(n.resolveTypeBinding())
          && (n.getOperator() == Assignment.Operator.DIVIDE_ASSIGN
              || n.getOperator() == Assignment.Operator.REMAINDER_ASSIGN)) {
        Collection<Pair<ITypeBinding, Object>> excTargets =
            context.getCatchTargets(fDivByZeroExcType);
        if (!excTargets.isEmpty()) {
          for (Pair<ITypeBinding, Object> catchPair : excTargets)
            context.cfg().add(result, catchPair.snd, fDivByZeroExcType);
        } else {
          context.cfg().add(result, CAstControlFlowMap.EXCEPTION_TO_EXIT, fDivByZeroExcType);
        }
      }

      return result;
    }
  }

  /**
   * Consider the case:
   *
   * <pre>
   * String real_one_hey_ya = (((returnObjectWithSideEffects().y))+=&quot;hey&quot;)+&quot;ya&quot;
   * </pre>
   *
   * where field 'y' is parameterized to type string. then += is not defined for type 'object'. This
   * function is a hack that expands the code into an assignment and binary operation.
   */
  private CAstNode doFunkyGenericAssignPreOpHack(Assignment assign, WalkContext context) {
    Expression left = assign.getLeftHandSide();
    Expression right = assign.getRightHandSide();

    // consider the case:
    // String real_one_hey_ya = (((returnObjectWithSideEffects().y))+="hey")+"ya"; // this is going
    // to
    // be a MAJOR pain...
    // where field 'y' is parameterized to type string. then += is not defined for type 'object'. we
    // want to transform
    // it kind of like this, except we have to define temp.
    // String real_one_hey_ya = (String)((temp=cg2WithSideEffects()).y = (String)temp.y +
    // "hey")+"ya";
    // ----------------------------------------------------------------
    //
    // we are responsible for underlined portion
    // CAST(LOCAL SCOPE(BLOCK EXPR(DECL STMT(temp,
    // left.target),ASSIGN(OBJECT_REF(temp,y),BINARY_EXPR(CAST(OBJECT_REF(Temp,y)),RIGHT)))))
    // yeah, I know, it's cheating, LOCAL SCOPE / DECL STMT inside an expression ... will it work?

    while (left instanceof ParenthesizedExpression)
      left = ((ParenthesizedExpression) left).getExpression();
    assert left instanceof FieldAccess : "Cast in assign pre-op but no field access?!";

    FieldAccess field = (FieldAccess) left;
    InfixExpression.Operator infixop =
        JDT2CAstUtils.mapAssignOperatorToInfixOperator(assign.getOperator());

    // DECL_STMT: temp = ...;
    final String tmpName = "temp generic preop hack"; // illegal Java identifier
    CAstNode exprNode = visitNode(field.getExpression(), context);
    CAstNode tmpDeclNode =
        makeNode(
            context,
            fFactory,
            left,
            CAstNode.DECL_STMT,
            fFactory.makeConstant(
                new InternalCAstSymbol(
                    tmpName,
                    fTypeDict.getCAstTypeFor(field.getExpression().resolveTypeBinding()),
                    true)),
            exprNode);

    // need two object refndoes "temp.y"
    CAstNode obref1 =
        createFieldAccess(
            makeNode(
                context,
                fFactory,
                left,
                CAstNode.VAR,
                fFactory.makeConstant(tmpName),
                fFactory.makeConstant(
                    fTypeDict.getCAstTypeFor(field.resolveFieldBinding().getType()))),
            field.getName().getIdentifier(),
            field.resolveFieldBinding(),
            left,
            new AssignmentContext(context));

    CAstNode obref2 =
        createFieldAccess(
            makeNode(
                context,
                fFactory,
                left,
                CAstNode.VAR,
                fFactory.makeConstant(tmpName),
                fFactory.makeConstant(
                    fTypeDict.getCAstTypeFor(field.resolveFieldBinding().getType()))),
            field.getName().getIdentifier(),
            field.resolveFieldBinding(),
            left,
            context);
    ITypeBinding realtype = JDT2CAstUtils.getErasedType(field.resolveFieldBinding().getType(), ast);
    ITypeBinding fromtype =
        JDT2CAstUtils.getTypesVariablesBase(
            field.resolveFieldBinding().getVariableDeclaration().getType(), ast);

    // put it all together
    // CAST(LOCAL SCOPE(BLOCK EXPR(DECL STMT(temp,
    // left.target),ASSIGN(OBJECT_REF(temp,y),BINARY_EXPR(CAST(OBJECT_REF(Temp,y)),RIGHT)))))
    CAstNode result =
        makeNode(
            context,
            fFactory,
            assign,
            CAstNode.LOCAL_SCOPE,
            makeNode(
                context,
                fFactory,
                assign,
                CAstNode.BLOCK_EXPR,
                tmpDeclNode,
                makeNode(
                    context,
                    fFactory,
                    assign,
                    CAstNode.ASSIGN,
                    obref1,
                    createInfixExpression(
                        infixop,
                        realtype,
                        left.getStartPosition(),
                        left.getLength(),
                        obref2,
                        right,
                        context))));

    return createCast(assign, result, fromtype, realtype, context);
  }

  private CAstNode visit(ParenthesizedExpression n, WalkContext context) {
    return visitNode(n.getExpression(), context);
  }

  private CAstNode visit(BooleanLiteral n) {
    return fFactory.makeConstant(n.booleanValue());
  }

  private CAstNode visit(CharacterLiteral n) {
    return fFactory.makeConstant(n.charValue());
  }

  private CAstNode visit() {
    return fFactory.makeConstant(null);
  }

  private CAstNode visit(StringLiteral n, WalkContext context) {
    CAstNode str = fFactory.makeConstant(n.getLiteralValue());
    setPos(context, str, n);
    return str;
  }

  private CAstNode visit(TypeLiteral n, WalkContext context) {
    String typeName = fIdentityMapper.typeToTypeID(n.resolveTypeBinding());
    return makeNode(
        context, fFactory, n, CAstNode.TYPE_LITERAL_EXPR, fFactory.makeConstant(typeName));
  }

  private CAstNode visit(NumberLiteral n, WalkContext context) {
    CAstNode str = fFactory.makeConstant(n.resolveConstantExpressionValue());
    setPos(context, str, n);
    return str;
  }

  /** SimpleName can be a field access, local, or class name (do nothing case) */
  private CAstNode visit(SimpleName n, WalkContext context) {
    // class name, handled above in either method invocation, qualified name, or qualified this
    if (n.resolveBinding() instanceof ITypeBinding)
      return makeNode(context, fFactory, null, CAstNode.EMPTY);

    assert n.resolveBinding() instanceof IVariableBinding
        : "SimpleName's binding, " + n.resolveBinding() + ", is not a variable or a type binding!";

    IVariableBinding binding = (IVariableBinding) n.resolveBinding();
    binding = binding.getVariableDeclaration(); // ignore weird generic stuff

    // TODO: enum constants
    if (binding.isField()) {
      // enum constants ...

      // implicit field access -- implicit this or class
      CAstNode targetNode;

      if ((binding.getModifiers() & Modifier.STATIC) != 0) {
        // "foo = 5" -> "MyClass.foo = 5" or "SomeEnclosingClass.foo" = 5
        targetNode =
            makeNode(
                context, fFactory, null, CAstNode.EMPTY); // we will get type from binding. no side
        // effects in evaluating a class name, so NOP
        // here.
      } else {
        ITypeBinding typeOfThis = JDT2CAstUtils.getDeclaringClassOfNode(n);

        boolean fieldIsPrivate = (binding.getModifiers() & Modifier.PRIVATE) != 0;

        // how could it be in the subtype and private? this only happens the supertype is also an
        // enclosing type. in that case the variable refers to the field in the enclosing instance.
        // NOTE: method may be defined in MyClass's superclass, but we still want to expand
        // this into MyClass, so we have to find the enclosing class which defines this function.

        ITypeBinding implicitThisClass =
            findClosestEnclosingClassSubclassOf(
                typeOfThis, binding.getDeclaringClass(), fieldIsPrivate);
        if (typeOfThis.isEqualTo(implicitThisClass))
          // "foo = 5" -> "this.foo = 5": expand into THIS + class
          targetNode = makeNode(context, fFactory, n, CAstNode.THIS);
        else
          // "foo = 5" -> "MyClass.this.foo = 5" -- inner class: expand into THIS + class
          targetNode =
              makeNode(
                  context,
                  fFactory,
                  n,
                  CAstNode.THIS,
                  fFactory.makeConstant(fIdentityMapper.getTypeRef(implicitThisClass)));
        // NOTE: method may be defined in MyClass's superclass, but we still want to expand
        // this into MyClass, so we have to find the enclosing class which defines this function.
        // fFactory.makeConstant(owningTypeRef));
      }
      return createFieldAccess(targetNode, n.getIdentifier(), binding, n, context);

    } else {
      // local
      CAstType t = fTypeDict.getCAstTypeFor(((IVariableBinding) n.resolveBinding()).getType());
      return makeNode(
          context,
          fFactory,
          n,
          CAstNode.VAR,
          fFactory.makeConstant(n.getIdentifier()),
          fFactory.makeConstant(t));
    }
  }

  /**
   * Sees if a field defined in owningTypeRef is contained & accessible to a type of typeOfThis.
   * That is, if owningTypeRef == typeOfThis or typeOfThis is a subtype and isPrivate is false. If
   * this is not that case, looks in the enclosing class of typeOfThis and tries again, and its
   * enclosing class, ...
   *
   * <p>Essentially if we have a field/method referenced only by name and we know its type
   * (owningTypeRef), this function will return owningTypeRef or the subtype that the field is
   * accessed thru, for expanding "f = 5" into "TheClass.this.f = 5".
   */
  private static ITypeBinding findClosestEnclosingClassSubclassOf(
      ITypeBinding typeOfThis, ITypeBinding owningType, boolean isPrivate) {
    // GENERICS
    //    if (owningType.isParameterizedType())
    //      owningType = owningType.getTypeDeclaration();
    //    if (typeOfThis.isParameterizedType())
    //      typeOfThis = typeOfThis.getTypeDeclaration();
    //    // typeOfThis.getTypeDeclaration()
    owningType = owningType.getErasure();

    ITypeBinding current = typeOfThis;
    while (current != null) {
      current = current.getErasure();
      // Walk the hierarchy rather than using isSubTypeCompatible to handle
      // generics -- we need to perform erasure of super types
      boolean isInSubtype = false; // current.isSubTypeCompatible(owningType);
      ITypeBinding supertp = current;
      while (supertp != null) {
        supertp = supertp.getErasure();
        // Use isSubTypeCompatible even though we are manually walking type hierarchy --
        // that way interfaces are handled without us having to do it manually.
        if (supertp.isSubTypeCompatible(owningType)) {
          isInSubtype = true;
          break;
        }
        supertp = supertp.getSuperclass();
      }

      // how could it be in the subtype and private? this only happens the supertype is also an
      // enclosing type. in that case the variable refers to the field in the enclosing instance.

      if (current.isEqualTo(owningType) || (isInSubtype && !isPrivate)) return current;

      current = current.getDeclaringClass();
    }

    Assertions.UNREACHABLE(
        "Couldn't find field in class or enclosing class or superclasses of these");
    return null;
  }

  /**
   * Process a field access. Semantics differ for static and instance fields. Fields can throw null
   * pointer exceptions so we must connect proper exceptional edges in the CFG.
   */
  private CAstNode visit(FieldAccess n, WalkContext context) {
    CAstNode targetNode = visitNode(n.getExpression(), context);
    return createFieldAccess(
        targetNode, n.getName().getIdentifier(), n.resolveFieldBinding(), n, context);
  }

  /**
   * Used by visit(FieldAccess) and visit(SimpleName) -- implicit "this" / static field access.
   * things from 'this' cannot throw an exception. maybe handle this in here as a special case? i
   * don't know... or check if targetNode is THIS, that should even work for this.x = 5 and (this).x
   * = 5
   *
   * @param targetNode Used to evaluate the field access. In the case of static field accesses, this
   *     is included in the first part of a block -- thus it is evaluated for any side effects but
   *     thrown away.
   * @param fieldName Name of the field.
   * @param positioningNode Used only for making a JdtPosition.
   */
  private CAstNode createFieldAccess(
      CAstNode targetNode,
      String fieldName,
      IVariableBinding possiblyParameterizedBinding,
      ASTNode positioningNode,
      WalkContext context) {

    IVariableBinding fieldBinding = possiblyParameterizedBinding.getVariableDeclaration();

    ITypeBinding targetType = fieldBinding.getDeclaringClass();

    if (targetType == null) { // array
      assert fieldName.equals("length") : "null targetType but not aray length access";
      return makeNode(context, fFactory, positioningNode, CAstNode.ARRAY_LENGTH, targetNode);
    }

    assert fieldBinding.isField()
        : "Field binding is not a field?!"; // we can probably safely delete this
    // check

    // translate JDT field ref to WALA field ref
    FieldReference fieldRef = fIdentityMapper.getFieldRef(fieldBinding);

    if ((fieldBinding.getModifiers() & Modifier.STATIC) != 0) {
      // JLS says: evaluate the target of the field ref and throw it away.
      // Hence the following block expr, whose 2 children are the target
      // evaluation
      // followed by the OBJECT_REF with a null target child (which the
      // "back-end"
      // CAst -> IR translator interprets as a static ref).
      // TODO: enum constants

      // don't worry about generics (can't declare static fields with type variables)
      if (fieldBinding.getConstantValue() != null) {
        return makeNode(
            context,
            fFactory,
            positioningNode,
            CAstNode.BLOCK_EXPR,
            targetNode,
            fFactory.makeConstant(fieldBinding.getConstantValue()));
      } else {
        return makeNode(
            context,
            fFactory,
            positioningNode,
            CAstNode.BLOCK_EXPR,
            targetNode,
            makeNode(
                context,
                fFactory,
                positioningNode,
                CAstNode.OBJECT_REF,
                makeNode(context, fFactory, null, CAstNode.VOID),
                fFactory.makeConstant(fieldRef)));
      }
    } else {
      CAstNode refNode =
          makeNode(
              context,
              fFactory,
              positioningNode,
              CAstNode.OBJECT_REF,
              targetNode,
              fFactory.makeConstant(fieldRef));

      if (targetNode.getKind()
          != CAstNode.THIS) { // this.x will never throw a null pointer exception, because this
        // can never be null
        Collection<Pair<ITypeBinding, Object>> excTargets =
            context.getCatchTargets(fNullPointerExcType);
        if (!excTargets.isEmpty()) {
          // connect NPE exception edge to relevant catch targets
          // (presumably only one)
          for (Pair<ITypeBinding, Object> catchPair : excTargets) {
            context.cfg().add(refNode, catchPair.snd, fNullPointerExcType);
          }
        } else {
          // connect exception edge to exit
          context.cfg().add(refNode, CAstControlFlowMap.EXCEPTION_TO_EXIT, fNullPointerExcType);
        }

        context.cfg().map(refNode, refNode);
      }

      if (fieldBinding.getConstantValue() != null) {
        // don't have to worry about generics, a constant of generic type can only be null
        return makeNode(
            context,
            fFactory,
            positioningNode,
            CAstNode.BLOCK_EXPR,
            refNode,
            // evaluating 'refNode' can have side effects, so we must still evaluate it!
            fFactory.makeConstant(fieldBinding.getConstantValue()));
      } else {
        if (fieldBinding.getType().isTypeVariable() && !context.needLValue()) {
          // GENERICS: add a cast
          ITypeBinding realtype =
              JDT2CAstUtils.getErasedType(possiblyParameterizedBinding.getType(), ast);
          ITypeBinding fromtype = JDT2CAstUtils.getTypesVariablesBase(fieldBinding.getType(), ast);
          if (!realtype.isEqualTo(fromtype))
            return createCast(positioningNode, refNode, fromtype, realtype, context);
        }
        return refNode;
      }
    }
  }

  private CAstNode visit(ThisExpression n, WalkContext context) {
    if (n.getQualifier() != null) {
      ITypeBinding owningType = n.getQualifier().resolveTypeBinding();
      TypeReference owningTypeRef = fIdentityMapper.getTypeRef(owningType);
      return makeNode(context, fFactory, n, CAstNode.THIS, fFactory.makeConstant(owningTypeRef));
    } else return makeNode(context, fFactory, n, CAstNode.THIS);
  }

  /**
   * QualifiedNames may be: 1) static of non-static field accesses -- we handle this case here 2)
   * type names used in the context of: a) field access (QualifiedName) b) method invocation c)
   * qualifier of "this" in these cases we get the binding info in each of these three functions and
   * use them there, thus we return an EMPTY (no-op) here. 3) package names used in the context of a
   * QualifiedName class we return a EMPTY (no-op) here.
   */
  private CAstNode visit(QualifiedName n, WalkContext context) {
    // "package.Class" is a QualifiedName, but also is "Class.staticField"
    // only handle if it's a "Class.staticField" ("Field" in polyglot AST)

    if (n.resolveBinding() instanceof IVariableBinding) {
      IVariableBinding binding = (IVariableBinding) n.resolveBinding();
      assert binding.isField() : "Non-field variable QualifiedName!";

      // if field access is static, visitNode(n.getQualifier()) will come back here
      // and we will return an EMPTY node
      return createFieldAccess(
          visitNode(n.getQualifier(), context), n.getName().getIdentifier(), binding, n, context);
    } else return makeNode(context, fFactory, null, CAstNode.EMPTY);
    // type name, handled in surrounding context
  }

  private CAstNode visit(InfixExpression n, WalkContext context) {
    Expression left = n.getLeftOperand();
    ITypeBinding leftType = left.resolveTypeBinding();
    int leftStartPosition = left.getStartPosition();

    CAstNode leftNode = visitNode(left, context);

    int leftLength = n.getLeftOperand().getLength();
    CAstNode result =
        createInfixExpression(
            n.getOperator(),
            leftType,
            leftStartPosition,
            leftLength,
            leftNode,
            n.getRightOperand(),
            context);

    if (n.hasExtendedOperands()) {
      // keep on adding operands on the right side

      leftLength =
          n.getRightOperand().getStartPosition()
              + n.getRightOperand().getLength()
              - leftStartPosition;
      for (Expression operand : (Iterable<Expression>) n.extendedOperands()) {
        result =
            createInfixExpression(
                n.getOperator(), leftType, leftStartPosition, leftLength, result, operand, context);

        if (leftType.isPrimitive() && operand.resolveTypeBinding().isPrimitive())
          leftType =
              JDT2CAstUtils.promoteTypes(
                  leftType, operand.resolveTypeBinding(), ast); // TODO: boxing
        else leftType = operand.resolveTypeBinding();

        // leftStartPosition doesn't change, beginning is always the first operand
        leftLength = operand.getStartPosition() + operand.getLength() - leftStartPosition;
      }
    }

    return result;
  }

  private CAstNode createInfixExpression(
      InfixExpression.Operator op,
      ITypeBinding leftType,
      int leftStartPosition,
      int leftLength,
      CAstNode leftNode,
      Expression right,
      WalkContext context) {
    CAstNode rightNode = visitNode(right, context);

    int end = right.getStartPosition() + right.getLength();
    T pos = makePosition(leftStartPosition, end);
    T leftPos = makePosition(leftStartPosition, leftStartPosition + leftLength);
    T rightPos =
        makePosition(right.getStartPosition(), right.getStartPosition() + right.getLength());

    if (op == InfixExpression.Operator.CONDITIONAL_AND) {
      return makeNode(
          context,
          fFactory,
          pos,
          CAstNode.IF_EXPR,
          leftNode,
          rightNode,
          fFactory.makeConstant(false));
    } else if (op == InfixExpression.Operator.CONDITIONAL_OR) {
      return makeNode(
          context,
          fFactory,
          pos,
          CAstNode.IF_EXPR,
          leftNode,
          fFactory.makeConstant(true),
          rightNode);
    } else {
      ITypeBinding rightType = right.resolveTypeBinding();
      if (leftType.isPrimitive() && rightType.isPrimitive()) {
        // TODO: boxing
        ITypeBinding result = JDT2CAstUtils.promoteTypes(leftType, rightType, ast);

        // cast to proper type
        if (!result.isEqualTo(leftType))
          leftNode =
              makeNode(
                  context,
                  fFactory,
                  leftPos,
                  CAstNode.CAST,
                  fFactory.makeConstant(fTypeDict.getCAstTypeFor(result)),
                  leftNode,
                  fFactory.makeConstant(fTypeDict.getCAstTypeFor(leftType)));
        if (!result.isEqualTo(rightType))
          rightNode =
              makeNode(
                  context,
                  fFactory,
                  rightPos,
                  CAstNode.CAST,
                  fFactory.makeConstant(fTypeDict.getCAstTypeFor(result)),
                  rightNode,
                  fFactory.makeConstant(fTypeDict.getCAstTypeFor(rightType)));

        CAstNode opNode =
            makeNode(
                context,
                fFactory,
                pos,
                CAstNode.BINARY_EXPR,
                JDT2CAstUtils.mapBinaryOpcode(op),
                leftNode,
                rightNode);

        // divide by zero exception implicitly thrown
        if (JDT2CAstUtils.isLongOrLess(leftType)
            && JDT2CAstUtils.isLongOrLess(rightType)
            && (JDT2CAstUtils.mapBinaryOpcode(op) == CAstOperator.OP_DIV
                || JDT2CAstUtils.mapBinaryOpcode(op) == CAstOperator.OP_MOD)) {
          Collection<Pair<ITypeBinding, Object>> excTargets =
              context.getCatchTargets(fDivByZeroExcType);
          if (!excTargets.isEmpty()) {
            for (Pair<ITypeBinding, Object> catchPair : excTargets) {
              context.cfg().add(op, catchPair.snd, fDivByZeroExcType);
            }
          } else {
            context.cfg().add(op, CAstControlFlowMap.EXCEPTION_TO_EXIT, fDivByZeroExcType);
          }
        }

        return opNode;

      } else {
        return makeNode(
            context,
            fFactory,
            pos,
            CAstNode.BINARY_EXPR,
            JDT2CAstUtils.mapBinaryOpcode(op),
            leftNode,
            rightNode);
      }
    }
  }

  private CAstNode visit(ConstructorInvocation n, WalkContext context) {
    // GENERICS getMethodDeclaration()
    return createConstructorInvocation(
        n.resolveConstructorBinding().getMethodDeclaration(), n.arguments(), n, context, false);
  }

  private CAstNode visit(SuperConstructorInvocation n, WalkContext context) {
    // FIXME: use expression?! polyglot doesn't handle it and it seems to be a very rare case.
    // class E { class X {} }
    // class Y extends E.X { Y(E e) { e.super(); } }
    // GENERICS getMethodDeclaration()
    return createConstructorInvocation(
        n.resolveConstructorBinding().getMethodDeclaration(), n.arguments(), n, context, true);
  }

  private CAstNode visit(SuperFieldAccess n, WalkContext context) {
    CAstNode targetNode;
    if (n.getQualifier() == null) targetNode = makeNode(context, fFactory, n, CAstNode.SUPER);
    else {
      TypeReference owningTypeRef =
          fIdentityMapper.getTypeRef(n.getQualifier().resolveTypeBinding());
      targetNode =
          makeNode(context, fFactory, n, CAstNode.SUPER, fFactory.makeConstant(owningTypeRef));
    }
    return createFieldAccess(
        targetNode, n.getName().getIdentifier(), n.resolveFieldBinding(), n, context);
  }

  /** callerNode: used for positioning and also in CFG (handleThrowsFrom Call) */
  private CAstNode createConstructorInvocation(
      IMethodBinding ctorBinding,
      List<Expression> arguments,
      ASTNode callerNode,
      WalkContext context,
      boolean isSuper) {
    ITypeBinding ctorType = ctorBinding.getDeclaringClass();
    assert ctorType.isClass();

    // dummy PC = 0 -- Just want to wrap the kind of call; the "rear end"
    // won't care about anything else...
    CallSiteReference callSiteRef =
        CallSiteReference.make(
            0, fIdentityMapper.getMethodRef(ctorBinding), IInvokeInstruction.Dispatch.SPECIAL);

    int nFormals = ctorBinding.getParameterTypes().length;
    List<CAstNode> children = new ArrayList<>(2 + nFormals);
    // this, call site ref, args

    CAstNode targetNode =
        makeNode(context, fFactory, callerNode, isSuper ? CAstNode.SUPER : CAstNode.THIS);

    children.add(targetNode);
    children.add(fFactory.makeConstant(callSiteRef));

    populateArguments(children, ctorBinding, arguments, context);

    handleThrowsFromCall(ctorBinding, callerNode, context);

    CAstNode result = makeNode(context, fFactory, callerNode, CAstNode.CALL, children);
    context.cfg().map(context, result);
    return result;
  }

  private CAstNode visit(IfStatement n, WalkContext context) {
    return makeNode(
        context,
        fFactory,
        n,
        CAstNode.IF_STMT,
        visitNode(n.getExpression(), context),
        visitNode(n.getThenStatement(), context),
        visitNode(n.getElseStatement(), context));
  }

  private CAstNode visit(InstanceofExpression n, WalkContext context) {
    return makeNode(
        context,
        fFactory,
        n,
        CAstNode.INSTANCEOF,
        fFactory.makeConstant(fTypeDict.getCAstTypeFor(n.getRightOperand().resolveBinding())),
        visitNode(n.getLeftOperand(), context));
  }

  private CAstNode visit(CastExpression n, WalkContext context) {
    Expression arg = n.getExpression();
    ITypeBinding castedFrom = arg.resolveTypeBinding();
    ITypeBinding castedTo = n.getType().resolveBinding();
    return createCast(n, visitNode(arg, context), castedFrom, castedTo, context);
  }

  private CAstNode createCast(
      ASTNode pos,
      CAstNode argNode,
      ITypeBinding castedFrom,
      ITypeBinding castedTo,
      WalkContext context) {
    Object cfgMapDummy =
        new Object(); // safer as 'pos' may be used for another purpose (i.e., this could be an
    // implicit cast)

    // null can go into anything (e.g. in "((Foobar) null)" null can be assumed to be of type Foobar
    // already)
    if (castedFrom.isNullType()) castedFrom = castedTo;

    CAstNode ast =
        makeNode(
            context,
            fFactory,
            pos,
            CAstNode.CAST,
            fFactory.makeConstant(fTypeDict.getCAstTypeFor(castedTo)),
            argNode,
            fFactory.makeConstant(fTypeDict.getCAstTypeFor(castedFrom)));

    Collection<Pair<ITypeBinding, Object>> excTargets = context.getCatchTargets(fClassCastExcType);
    if (!excTargets.isEmpty()) {
      // connect ClassCastException exception edge to relevant catch targets
      // (presumably only one)
      for (Pair<ITypeBinding, Object> catchPair : excTargets) {
        context.cfg().add(cfgMapDummy, catchPair.snd, fClassCastExcType);
      }
    } else {
      // connect exception edge to exit
      context.cfg().add(cfgMapDummy, CAstControlFlowMap.EXCEPTION_TO_EXIT, fClassCastExcType);
    }

    context.cfg().map(cfgMapDummy, ast);
    return ast;
  }

  private int ceCounter = 0;

  private CAstNode visit(ConditionalExpression n, WalkContext context) {
    String var = "ceTemporary" + ceCounter++;
    CAstNode declNode =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.DECL_STMT,
            fFactory.makeConstant(
                new InternalCAstSymbol(
                    var,
                    fTypeDict.getCAstTypeFor(n.getThenExpression().resolveTypeBinding()),
                    true)));

    context.addNameDecl(declNode);

    return makeNode(
        context,
        fFactory,
        n,
        CAstNode.BLOCK_EXPR,
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.IF_STMT,
            visitNode(n.getExpression(), context),
            makeNode(
                context,
                fFactory,
                n,
                CAstNode.ASSIGN,
                makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(var)),
                visitNode(n.getThenExpression(), context)),
            makeNode(
                context,
                fFactory,
                n,
                CAstNode.ASSIGN,
                makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(var)),
                visitNode(n.getElseExpression(), context))),
        makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(var)));
  }

  private CAstNode visit(PostfixExpression n, WalkContext context) {
    CAstOperator op =
        (n.getOperator() == PostfixExpression.Operator.DECREMENT)
            ? CAstOperator.OP_SUB
            : CAstOperator.OP_ADD;
    return makeNode(
        context,
        fFactory,
        n,
        CAstNode.ASSIGN_POST_OP,
        visitNode(n.getOperand(), context),
        fFactory.makeConstant(1),
        op);
  }

  private CAstNode visit(PrefixExpression n, WalkContext context) {
    PrefixExpression.Operator op = n.getOperator();

    if (op == PrefixExpression.Operator.DECREMENT || op == PrefixExpression.Operator.INCREMENT) {
      CAstOperator castOp =
          (n.getOperator() == PrefixExpression.Operator.DECREMENT)
              ? CAstOperator.OP_SUB
              : CAstOperator.OP_ADD;
      return makeNode(
          context,
          fFactory,
          n,
          CAstNode.ASSIGN_PRE_OP,
          visitNode(n.getOperand(), context),
          fFactory.makeConstant(1),
          castOp);
    } else if (op == PrefixExpression.Operator.PLUS) {
      return visitNode(n.getOperand(), context); // drop useless unary plus operator
    } else if (op == PrefixExpression.Operator.MINUS) {
      CAstNode zero;
      ITypeBinding type = n.getOperand().resolveTypeBinding();
      switch (type.getBinaryName()) {
        case "C":
          zero = fFactory.makeConstant((char) 0);
          break;
        case "B":
          zero = fFactory.makeConstant((byte) 0);
          break;
        case "S":
          zero = fFactory.makeConstant((short) 0);
          break;
        case "I":
          zero = fFactory.makeConstant(0);
          break;
        case "J":
          zero = fFactory.makeConstant(0L);
          break;
        case "F":
          zero = fFactory.makeConstant(0.0);
          break;
        case "D":
          zero = fFactory.makeConstant(0.0D);
          break;
        default:
          zero = null;
          assert false : "unexpected type " + type.getBinaryName();
          break;
      }
      return makeNode(
          context,
          fFactory,
          n,
          CAstNode.BINARY_EXPR,
          CAstOperator.OP_SUB,
          zero,
          visitNode(n.getOperand(), context));
    } else { // ! and ~
      CAstOperator castOp =
          (n.getOperator() == PrefixExpression.Operator.NOT)
              ? CAstOperator.OP_NOT
              : CAstOperator.OP_BITNOT;
      return makeNode(
          context, fFactory, n, CAstNode.UNARY_EXPR, castOp, visitNode(n.getOperand(), context));
    }
  }

  private CAstNode visit(EmptyStatement n, WalkContext context) {
    CAstNode result = makeNode(context, fFactory, n, CAstNode.EMPTY);
    context
        .cfg()
        .map(n, result); // why is this necessary? for break / continue targets? (they use an empty
    // statement)
    return result;
  }

  private CAstNode visit(AssertStatement n, WalkContext context) {
    return makeNode(context, fFactory, n, CAstNode.ASSERT, visitNode(n.getExpression(), context));
  }

  // ////////////////
  // LOOPS -- special handling of for and continue
  // ////////////////

  private CAstNode visit(LabeledStatement n, WalkContext context) {

    // find the first non-block statement ant set-up the label map (useful for breaking many fors)
    ASTNode stmt = n.getBody();
    while (stmt instanceof Block) stmt = (ASTNode) ((Block) stmt).statements().iterator().next();

    if (n.getParent() != null)
      // if not a synthetic node from a break/continue -- don't pollute namespace with label, we get
      // it thru the context
      context.getLabelMap().put(stmt, n.getLabel().getIdentifier());

    CAstNode result;
    if (!(n.getBody() instanceof EmptyStatement)) {
      ASTNode breakTarget = makeBreakOrContinueTarget(n, n.getLabel().getIdentifier());
      CAstNode breakNode = visitNode(breakTarget, context);
      WalkContext child = new BreakContext(context, n.getLabel().getIdentifier(), breakTarget);

      result =
          makeNode(
              context,
              fFactory,
              n,
              CAstNode.BLOCK_STMT,
              makeNode(
                  context,
                  fFactory,
                  n,
                  CAstNode.LABEL_STMT,
                  fFactory.makeConstant(n.getLabel().getIdentifier()),
                  visitNode(n.getBody(), child)),
              breakNode);
    } else {
      result =
          makeNode(
              context,
              fFactory,
              n,
              CAstNode.LABEL_STMT,
              fFactory.makeConstant(n.getLabel().getIdentifier()),
              visitNode(n.getBody(), context));
    }

    context.cfg().map(n, result);

    if (n.getParent() != null)
      // if not a synthetic node from a break/continue -- don't pollute namespace with label, we get
      // it thru the context
      context.getLabelMap().remove(stmt);

    return result;
  }

  /** Make a fake labeled node with no body, as an anchor to go to */
  private ASTNode makeBreakOrContinueTarget(ASTNode loop, String name) {
    LabeledStatement labeled = ast.newLabeledStatement();
    labeled.setBody(ast.newEmptyStatement());
    labeled.setSourceRange(loop.getStartPosition(), loop.getLength());
    labeled.setLabel(
        ast.newSimpleName(name)); // we don't have to worry about namespace conflicts as it is only
    // definedwithin
    return labeled;
  }

  private CAstNode visit(BreakStatement n, WalkContext context) {
    String label = n.getLabel() == null ? null : n.getLabel().getIdentifier();
    ASTNode target = context.getBreakFor(label);
    assert target != null;
    CAstNode result = makeNode(context, fFactory, n, CAstNode.GOTO);
    context.cfg().map(n, result);
    context.cfg().add(n, target, null);
    return result;
  }

  private CAstNode visit(ContinueStatement n, WalkContext context) {
    String label = n.getLabel() == null ? null : n.getLabel().getIdentifier();
    ASTNode target = context.getContinueFor(label);
    assert target != null;
    CAstNode result = makeNode(context, fFactory, n, CAstNode.GOTO);
    context.cfg().map(n, result);
    context.cfg().add(n, target, null);
    return result;
  }

  private CAstNode visit(WhileStatement n, WalkContext context) {
    Expression cond = n.getExpression();
    Statement body = n.getBody();

    ASTNode breakTarget = makeBreakOrContinueTarget(n, "breakLabel" + n.getStartPosition());
    CAstNode breakNode = visitNode(breakTarget, context);

    ASTNode continueTarget = makeBreakOrContinueTarget(n, "continueLabel" + n.getStartPosition());
    CAstNode continueNode = visitNode(continueTarget, context);

    String loopLabel = context.getLabelMap().get(n);
    LoopContext lc = new LoopContext(context, loopLabel, breakTarget, continueTarget);

    /*
     * The following loop is created sligtly differently than in jscore. It doesn't have a specific target for continue.
     */
    return makeNode(
        context,
        fFactory,
        n,
        CAstNode.BLOCK_STMT,
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.LOOP,
            visitNode(cond, context),
            makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, visitNode(body, lc), continueNode)),
        breakNode);
  }

  private CAstNode getSwitchCaseConstant(SwitchCase n, WalkContext context) {
    // TODO: enums
    @SuppressWarnings("deprecation")
    Expression expr = n.getExpression();
    Object constant =
        (expr == null)
            ? Integer.valueOf(0)
            : expr.resolveConstantExpressionValue(); // default case label of
    // "0" (what polyglot
    // does). we also set
    // SWITCH_DEFAULT
    // somewhere else
    // polyglot converts all labels to longs. why? who knows...
    if (constant instanceof Character) constant = (long) ((Character) constant).charValue();
    else if (constant instanceof Byte) constant = ((Byte) constant).longValue();
    else if (constant instanceof Integer) constant = ((Integer) constant).longValue();
    else if (constant instanceof Short) constant = ((Short) constant).longValue();

    if (constant != null) {
      return fFactory.makeConstant(constant);
    } else if (expr instanceof SimpleName) {
      // enum constant
      return visit((SimpleName) expr, context);
    } else {
      Assertions.UNREACHABLE("null constant for non-enum switch case!");
      return null;
    }
  }

  private CAstNode visit(SwitchCase n, WalkContext context) {
    CAstNode label =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.LABEL_STMT,
            getSwitchCaseConstant(n, context),
            makeNode(context, fFactory, n, CAstNode.EMPTY));

    context.cfg().map(n, label);
    return label;
  }

  private CAstNode visit(SwitchStatement n, WalkContext context) {
    ASTNode breakTarget = makeBreakOrContinueTarget(n, "breakLabel" + n.getStartPosition());
    CAstNode breakAst = visitNode(breakTarget, context);
    String loopLabel =
        context.getLabelMap().get(n); // set by labeled statement (if there is one before this
    // switch statement)
    WalkContext childContext = new BreakContext(context, loopLabel, breakTarget);
    Expression cond = n.getExpression();
    List<Statement> cases = n.statements();

    // First compute the control flow edges for the various case labels
    for (Statement se : cases) {
      if (se instanceof SwitchCase) {
        SwitchCase c = (SwitchCase) se;

        if (c.isDefault()) context.cfg().add(n, c, CAstControlFlowMap.SWITCH_DEFAULT);
        else context.cfg().add(n, c, getSwitchCaseConstant(c, context));
        // if we don't do this, we may not get a constant but a
        // block expression or something else
      }
    }

    ArrayList<CAstNode> caseNodes = new ArrayList<>();

    // polyglot bundles all statements in between two statements into a block.
    // this is temporary place to hold current bundle of nodes.
    ArrayList<CAstNode> currentBlock = new ArrayList<>();

    // Now produce the CAst representation for each case
    for (Statement s : cases) {
      if (s instanceof SwitchCase) {
        if (!currentBlock.isEmpty()) {
          // bundle up statements before this case
          List<CAstNode> stmtNodes = new ArrayList<>(currentBlock);
          // make position from start of first statement to end of last statement
          T positionOfAll =
              makePosition(
                  childContext.pos().getPosition(stmtNodes.get(0)).getFirstOffset(),
                  childContext
                      .pos()
                      .getPosition(stmtNodes.get(stmtNodes.size() - 1))
                      .getLastOffset());
          caseNodes.add(
              makeNode(childContext, fFactory, positionOfAll, CAstNode.BLOCK_STMT, stmtNodes));
          currentBlock.clear();
        }
        caseNodes.add(visitNode(s, childContext));
      } else {
        visitNodeOrNodes(s, childContext, currentBlock);
      }
    }
    if (!currentBlock.isEmpty()) {
      // bundle up statements before this case
      List<CAstNode> stmtNodes = new ArrayList<>(currentBlock);
      // make position from start of first statement to end of last statement
      T positionOfAll =
          makePosition(
              childContext.pos().getPosition(stmtNodes.get(0)).getFirstOffset(),
              childContext.pos().getPosition(stmtNodes.get(stmtNodes.size() - 1)).getLastOffset());
      caseNodes.add(
          makeNode(childContext, fFactory, positionOfAll, CAstNode.BLOCK_STMT, stmtNodes));
    }

    // Now produce the switch stmt itself
    CAstNode switchAst =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.SWITCH,
            visitNode(cond, context),
            makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, caseNodes));

    context.cfg().map(n, switchAst);

    // Finally, wrap the entire switch in a block so that we have a
    // well-defined place to 'break' to.
    return makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, switchAst, breakAst);
  }

  private CAstNode visit(DoStatement n, WalkContext context) {
    String loopLabel = context.getLabelMap().get(n); // set by visit(LabeledStatement)
    String token = loopLabel == null ? "at_" + n.getStartPosition() : loopLabel;

    ASTNode breakTarget = makeBreakOrContinueTarget(n, "breakLabel_" + token);
    CAstNode breakNode = visitNode(breakTarget, context);

    ASTNode continueTarget = makeBreakOrContinueTarget(n, "continueLabel_" + token);
    CAstNode continueNode = visitNode(continueTarget, context);

    CAstNode loopTest = visitNode(n.getExpression(), context);

    WalkContext loopContext = new LoopContext(context, loopLabel, breakTarget, continueTarget);
    CAstNode loopBody = visitNode(n.getBody(), loopContext);

    CAstNode madeNode =
        doLoopTranslator.translateDoLoop(loopTest, loopBody, continueNode, breakNode, context);
    context.pos().setPosition(madeNode, makePosition(n));
    return madeNode;
  }

  /**
   * Expands the form: for ( [final] Type var: iterable ) { ... } Into something equivalent to: for
   * ( Iterator iter = iterable.iter(); iter.hasNext(); ) { [final] Type var = (Type) iter.next();
   * ... } Or, in the case of an array: for ( int idx = 0; i &lt; iterable.length; i++ ) { [final]
   * Type var = iterable[idx]; ... } Except that the expression "iterable" is only evaluate once (or
   * is it?)
   */
  private CAstNode visit(EnhancedForStatement n, WalkContext context) {
    if (n.getExpression().resolveTypeBinding().isArray())
      return makeArrayEnhancedForLoop(n, context);
    else return makeIteratorEnhancedForLoop(n, context);
  }

  private CAstNode makeIteratorEnhancedForLoop(EnhancedForStatement n, WalkContext context) {
    // case 1: iterator
    CAstNode exprNode = visitNode(n.getExpression(), context);
    SingleVariableDeclaration svd = n.getParameter();
    Statement body = n.getBody();

    // expand into:

    // typical for loop:
    // { [inits]; while (cond) { [body]; [label continueTarget]; iters } [label breakTarget]
    // BLOCK(BLOCK(init1,init2,...),LOOP(cond,BLOCK(bodyblock,continueTarget,BLOCK(iter1,iter2,...))),breakTarget

    // in our case:
    // the only init is "Iterator iter = iterable.iter()"
    // cond is "iter.hasNext()"
    // bodyblock should be prepended with "[final] Type var = iter.next()" (put in the block that
    // body belongs to)
    // iter is null
    // continueTarget and breakTarget are the same as in a regular for loop
    // BLOCK(iterassign,LOOP(cond,BLOCK(paramassign,bodyblock,continueTarget)),breakTarget)

    final String tmpName =
        "iter tmp"; // this is an illegal Java identifier, we will use this variable to hold the
    // "invisible"
    // iterator

    /*-------- make "iter = iterator.iter()" ---------*/
    // make a fake method ref
    MethodReference iterMethodRef =
        fIdentityMapper.fakeMethodRefNoArgs(
            "Ljava/lang/Iterable;.iterator()Ljava/util/Iterator<TT;>;",
            "Ljava/lang/Iterable",
            "iterator",
            "Ljava/util/Iterator");
    CAstNode iterCallSiteRef =
        fFactory.makeConstant(
            CallSiteReference.make(0, iterMethodRef, IInvokeInstruction.Dispatch.INTERFACE));
    // Iterable.iter() throws no exceptions.

    CAstNode iterCallNode =
        makeNode(context, fFactory, n, CAstNode.CALL, exprNode, iterCallSiteRef);
    // handle runtimeexception
    Object o1 = new Object(); // dummy object used for mapping / exceptions
    for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(fRuntimeExcType))
      context.cfg().add(o1, catchTarget.snd, catchTarget.fst);
    context
        .cfg()
        .map(o1, iterCallNode); // TODO: this might not work, lots of calls in this one statement.

    CAstNode iterAssignNode =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.DECL_STMT,
            fFactory.makeConstant(
                new InternalCAstSymbol(
                    tmpName, fTypeDict.getCAstTypeFor(ast.resolveWellKnownType("int")), true)),
            iterCallNode);

    // MATCHUP: wrap in a block
    iterAssignNode = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, iterAssignNode);

    // TODO: TOTEST: using this and Iterable.hasNext() explicitly in same file.

    /*---------- cond: iter.hasNext(); -----------*/
    MethodReference hasNextMethodRef =
        fIdentityMapper.fakeMethodRefNoArgs(
            "Ljava/util/Iterator;.hasNext()Z", "Ljava/util/Iterator", "hasNext", "Z");
    CAstNode iterVar = makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(tmpName));
    CAstNode hasNextCallSiteRef =
        fFactory.makeConstant(
            CallSiteReference.make(0, hasNextMethodRef, IInvokeInstruction.Dispatch.INTERFACE));

    // throws no exceptions.
    CAstNode hasNextCallNode =
        makeNode(context, fFactory, n, CAstNode.CALL, iterVar, hasNextCallSiteRef);
    // handle runtimeexception
    Object o2 = new Object(); // dummy object used for mapping / exceptions
    for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(fRuntimeExcType))
      context.cfg().add(o2, catchTarget.snd, catchTarget.fst);
    context
        .cfg()
        .map(
            o2, hasNextCallNode); // TODO: this might not work, lots of calls in this one statement.

    /*---------- paramassign: var = (Type) iter.next() ---------*/
    MethodReference nextMethodRef =
        fIdentityMapper.fakeMethodRefNoArgs(
            "Ljava/util/Iterator;.next()TE;", "Ljava/util/Iterator", "next", "Ljava/lang/Object");
    CAstNode nextCallSiteRef =
        fFactory.makeConstant(
            CallSiteReference.make(0, nextMethodRef, IInvokeInstruction.Dispatch.INTERFACE));
    // throws no exceptions.
    CAstNode iterVar2 =
        makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(tmpName));
    CAstNode nextCallNode =
        makeNode(context, fFactory, n, CAstNode.CALL, iterVar2, nextCallSiteRef);
    for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(fRuntimeExcType))
      context.cfg().add(svd, catchTarget.snd, catchTarget.fst);
    context.cfg().map(svd, nextCallNode);

    // TODO: another cfg edge associated with svd! is this okay? prolly not... associate it with the
    // cast, somehow...
    CAstNode castedNode =
        createCast(
            svd,
            nextCallNode,
            ast.resolveWellKnownType("java.lang.Object"),
            svd.resolveBinding().getType(),
            context);

    Object defaultValue = JDT2CAstUtils.defaultValueForType(svd.resolveBinding().getType());
    CAstNode nextAssignNode =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.DECL_STMT,
            fFactory.makeConstant(
                new CAstSymbolImpl(
                    svd.getName().getIdentifier(),
                    fTypeDict.getCAstTypeFor(svd.resolveBinding().getType()),
                    (svd.getModifiers() & Modifier.FINAL) != 0,
                    defaultValue)),
            castedNode);

    /*----------- put it all together ----------*/
    ASTNode breakTarget = makeBreakOrContinueTarget(n, "breakLabel" + n.getStartPosition());
    ASTNode continueTarget = makeBreakOrContinueTarget(n, "continueLabel" + n.getStartPosition());
    String loopLabel = context.getLabelMap().get(n);
    WalkContext loopContext = new LoopContext(context, loopLabel, breakTarget, continueTarget);

    // LOCAL_SCOPE(BLOCK(iterassign,LOOP(cond,BLOCK(BLOCK(paramassign,bodyblock),continueTarget,BLOCK())),breakTarget))
    return makeNode(
        context,
        fFactory,
        n,
        CAstNode.LOCAL_SCOPE,
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.BLOCK_STMT,
            iterAssignNode,
            makeNode(
                context,
                fFactory,
                n,
                CAstNode.LOOP,
                hasNextCallNode,
                makeNode(
                    context,
                    fFactory,
                    n,
                    CAstNode.BLOCK_STMT,
                    makeNode(
                        context,
                        fFactory,
                        n,
                        CAstNode.LOCAL_SCOPE,
                        makeNode(
                            context,
                            fFactory,
                            n,
                            CAstNode.BLOCK_STMT,
                            nextAssignNode,
                            visitNode(body, loopContext))),
                    visitNode(continueTarget, context),
                    makeNode(context, fFactory, n, CAstNode.BLOCK_STMT))),
            visitNode(breakTarget, context)));
  }

  private CAstNode makeArrayEnhancedForLoop(EnhancedForStatement n, WalkContext context) {
    // ********* BEFORE:
    // for ( String x: doSomething() ) { ... }
    // ********* AFTER:
    // {
    // String tmpArray[] = doSomething();
    // for ( int tmpIndex = 0; i < tmpArray.length; tmpIndex++ ) {
    // String x = tmpArray[tmpIndex];
    // ...
    // }
    // }
    // simplest:
    // LOCAL_SCOPE(BLOCK(arrayDecl,indexDecl,LOOP(cond,BLOCK(nextAssign,bodyblock,continueTarget,iter)),breakTarget))
    // match up exactly:
    // LOCAL_SCOPE(BLOCK(arrayDecl,LOCAL_SCOPE(BLOCK(BLOCK(indexDecl),LOOP(cond,BLOCK(LOCAL_SCOPE(BLOCK(nextAssign,bodyblock)),continueTarget,BLOCK(iter))),breakTarget))))

    /*------ arrayDecl --------- String tmpArray[] = doSomething() ------*/
    final String tmpArrayName = "for temp array"; // illegal java identifier
    CAstNode exprNode = visitNode(n.getExpression(), context);
    CAstNode arrayDeclNode =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.DECL_STMT,
            fFactory.makeConstant(
                new InternalCAstSymbol(
                    tmpArrayName,
                    fTypeDict.getCAstTypeFor(n.getExpression().resolveTypeBinding()),
                    true)),
            exprNode);

    /*------ indexDecl --------- int tmpIndex = 0 ------*/
    final String tmpIndexName = "for temp index";
    CAstNode indexDeclNode =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.DECL_STMT,
            fFactory.makeConstant(
                new InternalCAstSymbol(
                    tmpIndexName, fTypeDict.getCAstTypeFor(ast.resolveWellKnownType("int")), true)),
            fFactory.makeConstant(Integer.valueOf(0)));

    /*------ cond ------------- tmpIndex < tmpArray.length ------*/
    CAstNode tmpArrayLengthNode =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.ARRAY_LENGTH,
            makeNode(
                context,
                fFactory,
                n,
                CAstNode.VAR,
                fFactory.makeConstant(tmpArrayName),
                fFactory.makeConstant(
                    fTypeDict.getCAstTypeFor(n.getExpression().resolveTypeBinding()))));
    CAstNode condNode =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.BINARY_EXPR,
            CAstOperator.OP_LT,
            makeNode(
                context,
                fFactory,
                n,
                CAstNode.VAR,
                fFactory.makeConstant(tmpIndexName),
                fFactory.makeConstant(JavaPrimitiveType.INT)),
            tmpArrayLengthNode);

    /*------ tmpIndexInc -------- tmpIndex++ ------*/
    CAstNode tmpArrayIncNode =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.ASSIGN_POST_OP,
            makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(tmpIndexName)),
            fFactory.makeConstant(1),
            CAstOperator.OP_ADD);

    /*------ tmpArrayAccess ----- String x = tmpArray[tmpIndex] ------*/
    CAstNode tmpArrayAccessNode =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.ARRAY_REF,
            makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(tmpArrayName)),
            fFactory.makeConstant(
                fIdentityMapper.getTypeRef(
                    n.getExpression().resolveTypeBinding().getComponentType())),
            makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(tmpIndexName)));

    SingleVariableDeclaration svd = n.getParameter();
    Object defaultValue = JDT2CAstUtils.defaultValueForType(svd.resolveBinding().getType());
    CAstNode nextAssignNode =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.DECL_STMT,
            fFactory.makeConstant(
                new CAstSymbolImpl(
                    svd.getName().getIdentifier(),
                    fTypeDict.getCAstTypeFor(
                        n.getExpression().resolveTypeBinding().getComponentType()),
                    (svd.getModifiers() & Modifier.FINAL) != 0,
                    defaultValue)),
            tmpArrayAccessNode);

    // LOCAL_SCOPE(BLOCK(arrayDecl,LOCAL_SCOPE(BLOCK(BLOCK(indexDecl),LOOP(cond,BLOCK(LOCAL_SCOPE(BLOCK(nextAssign,bodyblock)),continueTarget,BLOCK(iter))),breakTarget))))
    // more complicated than it has to be, but it matches up exactly with the Java expansion above.

    ASTNode breakTarget = makeBreakOrContinueTarget(n, "breakLabel" + n.getStartPosition());
    ASTNode continueTarget = makeBreakOrContinueTarget(n, "continueLabel" + n.getStartPosition());
    String loopLabel = context.getLabelMap().get(n);
    WalkContext loopContext = new LoopContext(context, loopLabel, breakTarget, continueTarget);

    return makeNode(
        context,
        fFactory,
        n,
        CAstNode.LOCAL_SCOPE,
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.BLOCK_STMT,
            arrayDeclNode,
            makeNode(
                context,
                fFactory,
                n,
                CAstNode.LOCAL_SCOPE,
                makeNode(
                    context,
                    fFactory,
                    n,
                    CAstNode.BLOCK_STMT,
                    makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, indexDeclNode),
                    makeNode(
                        context,
                        fFactory,
                        n,
                        CAstNode.LOOP,
                        condNode,
                        makeNode(
                            context,
                            fFactory,
                            n,
                            CAstNode.BLOCK_STMT,
                            makeNode(
                                context,
                                fFactory,
                                n,
                                CAstNode.LOCAL_SCOPE,
                                makeNode(
                                    context,
                                    fFactory,
                                    n,
                                    CAstNode.BLOCK_STMT,
                                    nextAssignNode,
                                    visitNode(n.getBody(), loopContext))),
                            visitNode(continueTarget, context),
                            makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, tmpArrayIncNode))),
                    visitNode(breakTarget, context)))));
  }

  private CAstNode visit(ForStatement n, WalkContext context) {
    ASTNode breakTarget = makeBreakOrContinueTarget(n, "breakLabel" + n.getStartPosition());
    ASTNode continueTarget = makeBreakOrContinueTarget(n, "continueLabel" + n.getStartPosition());
    String loopLabel = context.getLabelMap().get(n);
    WalkContext loopContext = new LoopContext(context, loopLabel, breakTarget, continueTarget);

    ArrayList<CAstNode> inits = new ArrayList<>();
    for (int i = 0; i < n.initializers().size(); i++) {
      ASTNode init = (ASTNode) n.initializers().get(i);
      if (init instanceof VariableDeclarationExpression) {
        for (ASTNode o : (Iterable<ASTNode>) ((VariableDeclarationExpression) init).fragments())
          inits.add(visitNode(o, context));
      } else inits.add(visitNode(init, context));
    }

    List<CAstNode> iters = new ArrayList<>(n.updaters().size());
    for (Object updater : n.updaters()) iters.add(visitNode((ASTNode) updater, context));

    CAstNode initsBlock = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, inits);
    CAstNode itersBlock = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, iters);

    // { [inits]; while (cond) { [body]; [label continueTarget]; iters } [label breakTarget]
    return makeNode(
        context,
        fFactory,
        n,
        CAstNode.LOCAL_SCOPE,
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.BLOCK_STMT,
            initsBlock,
            makeNode(
                context,
                fFactory,
                n,
                CAstNode.LOOP,
                visitNode(n.getExpression(), context),
                makeNode(
                    context,
                    fFactory,
                    n,
                    CAstNode.BLOCK_STMT,
                    visitNode(n.getBody(), loopContext),
                    visitNode(continueTarget, context),
                    itersBlock)),
            visitNode(breakTarget, context)));
  }

  private CAstNode visit(TryStatement n, WalkContext context) {
    List<CatchClause> catchBlocks = n.catchClauses();
    Block finallyBlock = n.getFinally();
    Block tryBlock = n.getBody();
    List<?> resources = n.resources();

    // try/resources
    if (resources != null && !resources.isEmpty()) {

      CAstNode[] body = new CAstNode[resources.size()];
      for (int i = 0; i < resources.size(); i++) {
        body[i] = visitNode((ASTNode) resources.get(i), context);
      }

      List<CAstNode> fb = new ArrayList<>();
      for (Object x : resources) {
        if (x instanceof VariableDeclarationExpression) {
          for (Object y : ((VariableDeclarationExpression) x).fragments()) {
            if (y instanceof VariableDeclarationFragment) {
              ITypeBinding object = ast.resolveWellKnownType("java.lang.Object");
              IMethodBinding m = null;
              ITypeBinding me = ((VariableDeclarationFragment) y).resolveBinding().getType();
              outer:
              while (!object.equals(me)) {
                for (IMethodBinding ourmet : me.getDeclaredMethods())
                  if (ourmet.getName().equals("close")) {
                    m = ourmet;
                    break outer; // there can only be one per class so don't bother looking for more
                  }

                me = me.getSuperclass();
              }

              CAstNode target =
                  fFactory.makeNode(
                      CAstNode.VAR,
                      fFactory.makeConstant(
                          ((VariableDeclarationFragment) y).resolveBinding().getName()));
              fb.add(createMethodInvocation(n, m, target, Collections.emptyList(), context));
            }
          }
        }
      }

      return makeNode(
          context,
          fFactory,
          n,
          CAstNode.BLOCK_STMT,
          fFactory.makeNode(CAstNode.BLOCK_STMT, body),
          makeNode(
              context,
              fFactory,
              n,
              CAstNode.UNWIND,
              visitNode(tryBlock, context),
              fFactory.makeNode(CAstNode.BLOCK_STMT, fb)));

      // try/finally
    } else if (catchBlocks.isEmpty()) {
      return makeNode(
          context,
          fFactory,
          n,
          CAstNode.UNWIND,
          visitNode(tryBlock, context),
          visitNode(finallyBlock, context));

      // try/catch/[finally]
    } else {
      TryCatchContext tc = new TryCatchContext(context, n);

      CAstNode tryNode = visitNode(tryBlock, tc);
      for (CatchClause catchClause : catchBlocks) {
        tryNode =
            makeNode(context, fFactory, n, CAstNode.TRY, tryNode, visitNode(catchClause, context));
      }

      // try/catch
      if (finallyBlock == null) {
        return tryNode;

        // try/catch/finally
      } else {
        return makeNode(
            context, fFactory, n, CAstNode.UNWIND, tryNode, visitNode(finallyBlock, context));
      }
    }
  }

  private CAstNode visit(CatchClause n, WalkContext context) {
    Block body = n.getBody();
    SingleVariableDeclaration formal = n.getException();

    CAstNode excDecl =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.CATCH,
            fFactory.makeConstant(formal.getName().getIdentifier()),
            visitNode(body, context));

    CAstNode declStmt =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.DECL_STMT,
            fFactory.makeConstant(
                new CAstSymbolImpl(
                    formal.getName().getIdentifier(),
                    fTypeDict.getCAstTypeFor(formal.getName().resolveTypeBinding()),
                    true)));

    CAstNode localScope =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.LOCAL_SCOPE,
            makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, declStmt, excDecl));

    context.cfg().map(n, excDecl);
    CAstType type =
        n.getException().getType() instanceof UnionType
            ? fTypeDict.getCAstTypeForUnion((UnionType) n.getException().getType())
            : fTypeDict.getCAstTypeFor(n.getException().resolveBinding().getType());
    context.getNodeTypeMap().add(excDecl, type);
    return localScope;
  }

  private CAstNode visit(ThrowStatement n, WalkContext context) {
    CAstNode result =
        makeNode(context, fFactory, n, CAstNode.THROW, visitNode(n.getExpression(), context));
    ITypeBinding label = n.getExpression().resolveTypeBinding();

    context.cfg().map(n, result);

    Collection<Pair<ITypeBinding, Object>> catchNodes = context.getCatchTargets(label);

    for (Pair<ITypeBinding, Object> catchNode : catchNodes) {
      context.cfg().add(n, catchNode.snd, catchNode.fst);
    }

    return result;
  }

  private void hookUpNPETargets(ASTNode n, WalkContext wc) {
    Collection<Pair<ITypeBinding, Object>> excTargets = wc.getCatchTargets(fNullPointerExcType);
    if (!excTargets.isEmpty()) {
      // connect NPE exception edge to relevant catch targets
      // (presumably only one)
      for (Pair<ITypeBinding, Object> catchPair : excTargets) {
        wc.cfg().add(n, catchPair.snd, fNullPointerExcType);
      }
    } else {
      // connect exception edge to exit
      wc.cfg().add(n, CAstControlFlowMap.EXCEPTION_TO_EXIT, fNullPointerExcType);
    }
  }

  //
  // ARRAYS
  //

  private CAstNode visit(ArrayAccess n, WalkContext context) {
    TypeReference eltTypeRef = fIdentityMapper.getTypeRef(n.resolveTypeBinding());

    CAstNode cast =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.ARRAY_REF,
            visitNode(n.getArray(), context),
            fFactory.makeConstant(eltTypeRef),
            visitNode(n.getIndex(), context));

    hookUpNPETargets(n, context);

    context.cfg().map(n, cast);

    return cast;
  }

  // FIXME: inner classes here, probably too...
  private CAstNode visit(ArrayCreation n, WalkContext context) {
    ITypeBinding newType = n.resolveTypeBinding();
    ArrayInitializer ai = n.getInitializer();
    assert newType.isArray();

    if (ai != null) {
      return visitNode(ai, context);
    } else {
      TypeReference arrayTypeRef = fIdentityMapper.getTypeRef(newType);

      List<Expression> dims = n.dimensions();
      List<CAstNode> args = new ArrayList<>(dims.size() + 1);

      args.add(fFactory.makeConstant(arrayTypeRef));
      for (Expression dimExpr : dims) {
        args.add(visitNode(dimExpr, context));
      }
      return makeNode(context, fFactory, n, CAstNode.NEW, args);
    }
  }

  private CAstNode visit(SynchronizedStatement n, WalkContext context) {
    CAstNode exprNode = visitNode(n.getExpression(), context);
    String exprName = fFactory.makeUnique();
    CAstNode declStmt =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.DECL_STMT,
            fFactory.makeConstant(
                new CAstSymbolImpl(
                    exprName,
                    fTypeDict.getCAstTypeFor(n.getExpression().resolveTypeBinding()),
                    true)),
            exprNode);

    CAstNode monitorEnterNode =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.MONITOR_ENTER,
            makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(exprName)));
    context.cfg().map(monitorEnterNode, monitorEnterNode);
    for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(fNullPointerExcType))
      context.cfg().add(monitorEnterNode, catchTarget.snd, catchTarget.fst);

    CAstNode bodyNodes = visitNode(n.getBody(), context);

    CAstNode monitorExitNode =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.MONITOR_EXIT,
            makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(exprName)));
    context.cfg().map(monitorExitNode, monitorExitNode);
    for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(fNullPointerExcType))
      context.cfg().add(monitorExitNode, catchTarget.snd, catchTarget.fst);

    CAstNode bigBody =
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.BLOCK_STMT,
            monitorEnterNode,
            makeNode(context, fFactory, n, CAstNode.UNWIND, bodyNodes, monitorExitNode));

    return makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, declStmt, bigBody);
  }

  // ///////////////////////////////////////////
  // / THE GIANT SWITCH STATEMENT ( BORING ) ///
  // ///////////////////////////////////////////

  /** Giant switch statement. */
  private CAstEntity visit(AbstractTypeDeclaration n, WalkContext context) {
    // handling of compilationunit in translate()
    if (n instanceof TypeDeclaration) {
      return visitTypeDecl(n, context);
    } else if (n instanceof EnumDeclaration) {
      return visit((EnumDeclaration) n, context);
    } else if (n instanceof AnnotationTypeDeclaration) {
      return visitTypeDecl(n, context);
    } else {
      Assertions.UNREACHABLE("Unhandled type declaration type");
      return null;
    }
  }

  /** Giant switch statement, part deux */
  private CAstNode visitNode(ASTNode n, WalkContext context) {
    if (n == null) return makeNode(context, fFactory, null, CAstNode.EMPTY);

    if (n instanceof ArrayAccess) {
      return visit((ArrayAccess) n, context);
    } else if (n instanceof ArrayCreation) {
      return visit((ArrayCreation) n, context);
    } else if (n instanceof ArrayInitializer) {
      return visit((ArrayInitializer) n, context);
    } else if (n instanceof AssertStatement) {
      return visit((AssertStatement) n, context);
    } else if (n instanceof Assignment) {
      return visit((Assignment) n, context);
    } else if (n instanceof Block) {
      return visit((Block) n, context);
    } else if (n instanceof BooleanLiteral) {
      return visit((BooleanLiteral) n);
    } else if (n instanceof BreakStatement) {
      return visit((BreakStatement) n, context);
    } else if (n instanceof CastExpression) {
      return visit((CastExpression) n, context);
    } else if (n instanceof CatchClause) {
      return visit((CatchClause) n, context);
    } else if (n instanceof CharacterLiteral) {
      return visit((CharacterLiteral) n);
    } else if (n instanceof ClassInstanceCreation) {
      return visit((ClassInstanceCreation) n, context);
    } else if (n instanceof ConditionalExpression) {
      return visit((ConditionalExpression) n, context);
    } else if (n instanceof ConstructorInvocation) {
      return visit((ConstructorInvocation) n, context);
    } else if (n instanceof ContinueStatement) {
      return visit((ContinueStatement) n, context);
    } else if (n instanceof DoStatement) {
      return visit((DoStatement) n, context);
    } else if (n instanceof EmptyStatement) {
      return visit((EmptyStatement) n, context);
    } else if (n instanceof EnhancedForStatement) {
      return visit((EnhancedForStatement) n, context);
    } else if (n instanceof ExpressionStatement) {
      return visit((ExpressionStatement) n, context);
    } else if (n instanceof FieldAccess) {
      return visit((FieldAccess) n, context);
    } else if (n instanceof ForStatement) {
      return visit((ForStatement) n, context);
    } else if (n instanceof IfStatement) {
      return visit((IfStatement) n, context);
    } else if (n instanceof InfixExpression) {
      return visit((InfixExpression) n, context);
    } else if (n instanceof InstanceofExpression) {
      return visit((InstanceofExpression) n, context);
    } else if (n instanceof LabeledStatement) {
      return visit((LabeledStatement) n, context);
    } else if (n instanceof MethodInvocation) {
      return visit((MethodInvocation) n, context);
    } else if (n instanceof NumberLiteral) {
      return visit((NumberLiteral) n, context);
    } else if (n instanceof NullLiteral) {
      return visit();
    } else if (n instanceof ParenthesizedExpression) {
      return visit((ParenthesizedExpression) n, context);
    } else if (n instanceof PostfixExpression) {
      return visit((PostfixExpression) n, context);
    } else if (n instanceof PrefixExpression) {
      return visit((PrefixExpression) n, context);
    } else if (n instanceof QualifiedName) {
      return visit((QualifiedName) n, context);
    } else if (n instanceof ReturnStatement) {
      return visit((ReturnStatement) n, context);
    } else if (n instanceof SimpleName) {
      return visit((SimpleName) n, context);
    } else if (n instanceof StringLiteral) {
      return visit((StringLiteral) n, context);
    } else if (n instanceof SuperConstructorInvocation) {
      return visit((SuperConstructorInvocation) n, context);
    } else if (n instanceof SuperFieldAccess) {
      return visit((SuperFieldAccess) n, context);
    } else if (n instanceof SuperMethodInvocation) {
      return visit((SuperMethodInvocation) n, context);
    } else if (n instanceof SynchronizedStatement) {
      return visit((SynchronizedStatement) n, context);
    } else if (n instanceof SwitchStatement) {
      return visit((SwitchStatement) n, context);
    } else if (n instanceof SwitchCase) {
      return visit((SwitchCase) n, context);
    } else if (n instanceof ThisExpression) {
      return visit((ThisExpression) n, context);
    } else if (n instanceof TypeLiteral) {
      return visit((TypeLiteral) n, context);
    } else if (n instanceof ThrowStatement) {
      return visit((ThrowStatement) n, context);
    } else if (n instanceof TryStatement) {
      return visit((TryStatement) n, context);
    } else if (n instanceof TypeDeclarationStatement) {
      return visit((TypeDeclarationStatement) n, context);
    } else if (n instanceof VariableDeclarationExpression) {
      return visit((VariableDeclarationExpression) n, context);
    } else if (n instanceof VariableDeclarationFragment) {
      return visit((VariableDeclarationFragment) n, context);
    } else if (n instanceof WhileStatement) {
      return visit((WhileStatement) n, context);
    } else if (n instanceof LambdaExpression) {
      return visit((LambdaExpression) n, context);
    }

    // VariableDeclarationStatement handled as special case (returns multiple statements)

    Assertions.UNREACHABLE("Unhandled JDT node type " + n.getClass().getCanonicalName());

    return null;
  }

  private void visitNodeOrNodes(ASTNode n, WalkContext context, Collection<CAstNode> coll) {
    if (n instanceof VariableDeclarationStatement)
      coll.addAll(visit((VariableDeclarationStatement) n, context));
    else coll.add(visitNode(n, context));
  }

  // /////////////////////////////////////////
  // SPECIALIZED CASTENTITYs AND CASTNODEs //
  // /////////////////////////////////////////

  protected static final class CompilationUnitEntity implements CAstEntity {
    private final String fName;

    private final Collection<CAstEntity> fTopLevelDecls;

    public CompilationUnitEntity(
        PackageDeclaration packageDeclaration, List<CAstEntity> topLevelDecls) {
      fName =
          (packageDeclaration == null)
              ? ""
              : packageDeclaration.getName().getFullyQualifiedName().replace('.', '/');
      fTopLevelDecls = topLevelDecls;
    }

    @Override
    public Collection<CAstAnnotation> getAnnotations() {
      return null;
    }

    @Override
    public int getKind() {
      return FILE_ENTITY;
    }

    @Override
    public String getName() {
      return fName;
    }

    @Override
    public String getSignature() {
      Assertions.UNREACHABLE();
      return null;
    }

    @Override
    public String[] getArgumentNames() {
      return new String[0];
    }

    @Override
    public CAstNode[] getArgumentDefaults() {
      return new CAstNode[0];
    }

    @Override
    public int getArgumentCount() {
      return 0;
    }

    @Override
    public Map<CAstNode, Collection<CAstEntity>> getAllScopedEntities() {
      return Collections.singletonMap(null, fTopLevelDecls);
    }

    @Override
    public Iterator<CAstEntity> getScopedEntities(CAstNode construct) {
      Assertions.UNREACHABLE(
          "CompilationUnitEntity asked for AST-related entities, but it has no AST.");
      return null;
    }

    @Override
    public CAstNode getAST() {
      return null;
    }

    @Override
    public CAstControlFlowMap getControlFlow() {
      Assertions.UNREACHABLE("CompilationUnitEntity.getControlFlow()");
      return null;
    }

    @Override
    public CAstSourcePositionMap getSourceMap() {
      Assertions.UNREACHABLE("CompilationUnitEntity.getSourceMap()");
      return null;
    }

    @Override
    public CAstSourcePositionMap.Position getPosition() {
      return null;
    }

    @Override
    public CAstNodeTypeMap getNodeTypeMap() {
      Assertions.UNREACHABLE("CompilationUnitEntity.getNodeTypeMap()");
      return null;
    }

    @Override
    public Collection<CAstQualifier> getQualifiers() {
      return Collections.emptyList();
    }

    @Override
    public CAstType getType() {
      Assertions.UNREACHABLE("CompilationUnitEntity.getType()");
      return null;
    }

    @Override
    public Position getPosition(int arg) {
      return null;
    }

    @Override
    public Position getNamePosition() {
      return null;
    }
  }

  // /////////////////////////////
  // WALK CONTEXTS
  // WHY????
  // ////////////////////////////////

  /**
   * Contains things needed by in the visit() of some nodes to process the nodes. For example, pos()
   * contains the source position mapping which each node registers
   */
  public interface WalkContext extends TranslatorToCAst.WalkContext<WalkContext, ASTNode> {

    Collection<Pair<ITypeBinding, Object>> getCatchTargets(ITypeBinding type);

    Map<ASTNode, String> getLabelMap();

    boolean needLValue();
  }

  /**
   * Default context functions. When one context doesn't handle something, it the next one up does.
   * For example, there is only one source pos. mapping per MethodContext, so loop contexts delegate
   * it up.
   */
  public static class DelegatingContext
      extends TranslatorToCAst.DelegatingContext<WalkContext, ASTNode> implements WalkContext {

    public DelegatingContext(WalkContext parent) {
      super(parent);
    }

    @Override
    public Collection<Pair<ITypeBinding, Object>> getCatchTargets(ITypeBinding type) {
      return parent.getCatchTargets(type);
    }

    @Override
    public Map<ASTNode, String> getLabelMap() {
      return parent.getLabelMap();
    }

    @Override
    public boolean needLValue() {
      return parent.needLValue();
    }
  }

  /*
   * Root context. Doesn't do anything.
   */
  public static class RootContext extends TranslatorToCAst.RootContext<WalkContext, ASTNode>
      implements WalkContext {
    @Override
    public Collection<Pair<ITypeBinding, Object>> getCatchTargets(ITypeBinding type) {
      Assertions.UNREACHABLE("RootContext.getCatchTargets()");
      return null;
    }

    @Override
    public Map<ASTNode, String> getLabelMap() {
      Assertions.UNREACHABLE("RootContext.getLabelMap()");
      return null;
    }

    @Override
    public boolean needLValue() {
      Assertions.UNREACHABLE("Rootcontext.needLValue()");
      return false;
    }
  }

  private static class AssignmentContext extends DelegatingContext {

    protected AssignmentContext(WalkContext parent) {
      super(parent);
    }

    @Override
    public boolean needLValue() {
      return true;
    }
  }

  private static class TryCatchContext extends DelegatingContext {
    Collection<Pair<ITypeBinding, Object>> fCatchNodes = new ArrayList<>();

    TryCatchContext(WalkContext parent, TryStatement tryNode) {
      super(parent);

      for (CatchClause c : (Iterable<CatchClause>) tryNode.catchClauses()) {
        Pair<ITypeBinding, Object> p =
            Pair.make(c.getException().resolveBinding().getType(), (Object) c);

        fCatchNodes.add(p);
      }
    }

    @Override
    public Collection<Pair<ITypeBinding, Object>> getCatchTargets(ITypeBinding label) {
      // Look for all matching targets for this thrown type:
      // if supertpe match, then return only matches at this catch
      // if subtype match, then matches here and parent matches
      Collection<Pair<ITypeBinding, Object>> catchNodes = new ArrayList<>();

      for (Pair<ITypeBinding, Object> p : fCatchNodes) {
        ITypeBinding catchType = p.fst;

        // catchType here should NEVER be FakeExceptionTypeBinary, because these can only be thrown
        // (not caught) by
        // "1/0", implicit null pointer exceptions, etc.
        assert !(catchNodes instanceof FakeExceptionTypeBinding)
            : "catchNodes instanceof FakeExceptionTypeBinary!";

        if (label.isSubTypeCompatible(catchType) || label.isEqualTo(catchType)) {
          catchNodes.add(p);
          return catchNodes;
          // _might_ get caught
        } else if (catchType.isSubTypeCompatible(label)) {
          catchNodes.add(p);
        }
      }
      catchNodes.addAll(parent.getCatchTargets(label));
      return catchNodes;
    }
  }

  private static class BreakContext extends DelegatingContext {
    protected final String label;

    private final ASTNode breakTo;

    BreakContext(WalkContext parent, String label, ASTNode breakTo) {
      super(parent);
      this.label = label;
      this.breakTo = breakTo;
    }

    @Override
    public ASTNode getBreakFor(String label) {
      return (label == null || label.equals(this.label)) ? breakTo : super.getBreakFor(label);
    }
  }

  private static class LoopContext extends BreakContext {
    private final ASTNode continueTo;

    protected LoopContext(WalkContext parent, String label, ASTNode breakTo, ASTNode continueTo) {
      super(parent, label, breakTo);
      this.continueTo = continueTo;
    }

    @Override
    public ASTNode getContinueFor(String label) {
      return (label == null || label.equals(this.label)) ? continueTo : super.getContinueFor(label);
    }
  }

  public class MethodContext extends DelegatingContext {
    private final Map<CAstNode, CAstEntity> fEntities;

    private final Map<ASTNode, String> labelMap = HashMapFactory.make(2);

    public MethodContext(WalkContext parent, Map<CAstNode, CAstEntity> entities) {
      // constructor did take: pd.procedureInstance(), memberEntities, context
      super(parent);
      fEntities = entities;
    }

    private final List<CAstNode> initializers = new ArrayList<>();

    @Override
    public void addNameDecl(CAstNode v) {
      initializers.add(v);
    }

    @Override
    public List<CAstNode> getNameDecls() {
      return initializers;
    }

    @Override
    public Map<ASTNode, String> getLabelMap() {
      return labelMap; // labels are kept within a method.
    }

    final CAstSourcePositionRecorder fSourceMap = new CAstSourcePositionRecorder();

    final CAstControlFlowRecorder fCFG = new CAstControlFlowRecorder(fSourceMap);

    final CAstNodeTypeMapRecorder fNodeTypeMap = new CAstNodeTypeMapRecorder();

    @Override
    public CAstControlFlowRecorder cfg() {
      return fCFG;
    }

    @Override
    public void addScopedEntity(CAstNode node, CAstEntity entity) {
      fEntities.put(node, entity);
    }

    @Override
    public CAstSourcePositionRecorder pos() {
      return fSourceMap;
    }

    @Override
    public CAstNodeTypeMapRecorder getNodeTypeMap() {
      return fNodeTypeMap;
    }

    @Override
    public Collection<Pair<ITypeBinding, Object>> getCatchTargets(ITypeBinding label) {
      // TAGALONG (need fRuntimeExcType)
      // Why do we seemingly catch a RuntimeException in every method? this won't catch the
      // RuntimeException above where
      // it is supposed to be caught?
      return Collections.singleton(
          Pair.<ITypeBinding, Object>make(fRuntimeExcType, CAstControlFlowMap.EXCEPTION_TO_EXIT));
    }

    @Override
    public boolean needLValue() {
      return false;
    }
  }

  // ////////////////////////////////////
  // MAKE NODE VARIATIONS & POSITIONS (BORING)
  // maybe moved to different file.
  // makeNode() simply calls Ast.makeNode() and sets the position for the node
  // ////////////////////////////////////
  protected CAstNode makeNode(WalkContext wc, CAst Ast, ASTNode n, int kind) {
    CAstNode cn = Ast.makeNode(kind);
    setPos(wc, cn, n);
    return cn;
  }

  protected CAstNode makeNode(WalkContext wc, CAst Ast, ASTNode n, int kind, List<CAstNode> c) {
    CAstNode cn = Ast.makeNode(kind, c);
    setPos(wc, cn, n);
    return cn;
  }

  protected CAstNode makeNode(WalkContext wc, CAst Ast, T pos, int kind, List<CAstNode> c) {
    CAstNode cn = Ast.makeNode(kind, c);
    wc.pos().setPosition(cn, pos);
    return cn;
  }

  protected CAstNode makeNode(
      WalkContext wc, CAst Ast, ASTNode n, int kind, CAstNode c1, CAstNode c2) {
    CAstNode cn = Ast.makeNode(kind, c1, c2);
    setPos(wc, cn, n);
    return cn;
  }

  protected CAstNode makeNode(WalkContext wc, CAst Ast, ASTNode n, int kind, CAstNode c) {
    CAstNode cn = Ast.makeNode(kind, c);
    setPos(wc, cn, n);
    return cn;
  }

  protected CAstNode makeNode(
      WalkContext wc, CAst Ast, ASTNode n, int kind, CAstNode c1, CAstNode c2, CAstNode c3) {
    CAstNode cn = Ast.makeNode(kind, c1, c2, c3);
    setPos(wc, cn, n);
    return cn;
  }

  protected CAstNode makeNode(
      WalkContext wc,
      CAst Ast,
      ASTNode n,
      int kind,
      CAstNode c1,
      CAstNode c2,
      CAstNode c3,
      CAstNode c4) {
    CAstNode cn = Ast.makeNode(kind, c1, c2, c3, c4);
    setPos(wc, cn, n);
    return cn;
  }

  protected CAstNode makeNode(
      WalkContext wc, CAst Ast, T pos, int kind, CAstNode c1, CAstNode c2, CAstNode c3) {
    CAstNode cn = Ast.makeNode(kind, c1, c2, c3);
    wc.pos().setPosition(cn, pos);
    return cn;
  }

  protected void setPos(WalkContext wc, CAstNode cn, ASTNode jdtNode) {
    if (jdtNode != null) wc.pos().setPosition(cn, makePosition(jdtNode));
  }

  public T makePosition(ASTNode n) {
    return makePosition(n.getStartPosition(), n.getStartPosition() + n.getLength());
  }

  public abstract T makePosition(int start, int end);

  // /////////////////////////////////////////////////////////////////
  // // ENUM TRANSFORMATION //////////////////////////////////////////
  // /////////////////////////////////////////////////////////////////

  private static final ArrayList<CAstQualifier> enumQuals = new ArrayList<>(3);

  static {
    enumQuals.add(CAstQualifier.PUBLIC);
    enumQuals.add(CAstQualifier.STATIC);
    enumQuals.add(CAstQualifier.FINAL);
  }

  /** Only called from createClassDeclaration. */
  private CAstEntity visit(EnumConstantDeclaration decl) {
    return new FieldEntity(
        decl.getName().getIdentifier(),
        decl.resolveVariable().getType(),
        enumQuals,
        makePosition(decl.getStartPosition(), decl.getStartPosition() + decl.getLength()),
        null,
        makePosition(decl.getName()));
  }

  /** Called only from visitFieldInitNode(node,context) */
  private CAstNode createEnumConstantDeclarationInit(
      EnumConstantDeclaration node, WalkContext context) {
    String hiddenVariableName =
        (String) node.getProperty("com.ibm.wala.cast.java.translator.jdt.fakeValuesDeclName");
    if (hiddenVariableName == null) {
      FieldReference fieldRef = fIdentityMapper.getFieldRef(node.resolveVariable());
      // We use null to indicate an OBJECT_REF to a static field
      CAstNode lhsNode =
          makeNode(
              context,
              fFactory,
              node,
              CAstNode.OBJECT_REF,
              makeNode(context, fFactory, null, CAstNode.VOID),
              fFactory.makeConstant(fieldRef));

      // CONSTRUCT ARGUMENTS & "new MyEnum(...)" statement
      ArrayList<Object> arguments = new ArrayList<>();
      arguments.add(fFactory.makeConstant(node.getName().getIdentifier())); // name of constant
      arguments.add(fFactory.makeConstant(node.resolveVariable().getVariableId())); // id
      arguments.addAll(node.arguments());
      CAstNode rhsNode =
          createClassInstanceCreation(
              node,
              arguments,
              node.resolveConstructorBinding(),
              null,
              node.getAnonymousClassDeclaration(),
              context);

      return makeNode(context, fFactory, node, CAstNode.ASSIGN, lhsNode, rhsNode);
    } else {

      // String[] x = (new Direction[] {
      // NORTH, EAST, SOUTH, WEST, $VALUES, $VALUES$
      // });

      return null;
    }
  }

  private CAstEntity createEnumValueOfMethod(ITypeBinding enumType, WalkContext oldContext) {
    IMethodBinding met = null, superMet = null;
    // find our valueOf(String)
    for (IMethodBinding m : enumType.getDeclaredMethods())
      if (m.getName().equals("valueOf")
          && m.getParameterTypes().length == 1
          && m.getParameterTypes()[0].isEqualTo(ast.resolveWellKnownType("java.lang.String")))
        met = m;
    // find Enum.valueOf(Class, String)
    for (IMethodBinding m : enumType.getSuperclass().getTypeDeclaration().getDeclaredMethods())
      if (m.getName().equals("valueOf") && m.getParameterTypes().length == 2) superMet = m;
    assert met != null && superMet != null
        : "Couldn't find enum values() function in JDT bindings!";

    Map<CAstNode, CAstEntity> memberEntities = new LinkedHashMap<>();
    final MethodContext context = new MethodContext(oldContext, memberEntities);

    MethodDeclaration fakeMet = ast.newMethodDeclaration();
    fakeMet.setName(ast.newSimpleName("valueOf"));
    fakeMet.setSourceRange(-1, 0);
    fakeMet.setBody(ast.newBlock());
    SingleVariableDeclaration stringS = ast.newSingleVariableDeclaration();
    stringS.setName(ast.newSimpleName("s"));
    fakeMet.parameters().add(stringS);

    // TODO: probably uses reflection so isn't very useful for analyses. Is there something more
    // useful we could put in here?
    // return (MyEnum)Enum.valueOf(MyEnum.class, s);
    // cast(call(type_literal, var)))

    CAstNode typeLit =
        makeNode(
            context,
            fFactory,
            fakeMet,
            CAstNode.TYPE_LITERAL_EXPR,
            fFactory.makeConstant(fIdentityMapper.typeToTypeID(enumType)));
    CAstNode stringSvar =
        makeNode(
            context,
            fFactory,
            fakeMet,
            CAstNode.VAR,
            fFactory.makeConstant("s"),
            fFactory.makeConstant(
                fTypeDict.getCAstTypeFor(ast.resolveWellKnownType("java.lang.String"))));
    ArrayList<Object> args = new ArrayList<>();
    args.add(typeLit);
    args.add(stringSvar);
    CAstNode call =
        createMethodInvocation(
            fakeMet, superMet, makeNode(context, fFactory, fakeMet, CAstNode.VOID), args, context);
    CAstNode cast = createCast(fakeMet, call, enumType, superMet.getReturnType(), context);
    CAstNode bodyNode =
        makeNode(
            context,
            fFactory,
            fakeMet,
            CAstNode.LOCAL_SCOPE,
            makeNode(
                context,
                fFactory,
                fakeMet,
                CAstNode.BLOCK_STMT,
                makeNode(context, fFactory, fakeMet, CAstNode.RETURN, cast)));

    List<CAstType> paramTypes = new ArrayList<>(1);
    paramTypes.add(fTypeDict.getCAstTypeFor(ast.resolveWellKnownType("java.lang.String")));

    return new ProcedureEntity(
        bodyNode,
        fakeMet,
        enumType,
        memberEntities,
        context,
        paramTypes,
        enumType,
        met.getModifiers(),
        handleAnnotations(met),
        null);
  }

  private CAstEntity createEnumValuesMethod(
      ITypeBinding enumType, ArrayList<IVariableBinding> constants, WalkContext oldContext) {
    IMethodBinding met = null;
    for (IMethodBinding m : enumType.getDeclaredMethods())
      if (m.getName().equals("values") && m.getParameterTypes().length == 0) met = m;
    assert met != null : "Couldn't find enum values() function in JDT bindings!";

    Map<CAstNode, CAstEntity> memberEntities = new LinkedHashMap<>();
    final MethodContext context = new MethodContext(oldContext, memberEntities);

    MethodDeclaration fakeMet = ast.newMethodDeclaration();
    fakeMet.setName(ast.newSimpleName("values"));
    fakeMet.setSourceRange(-1, 0);
    fakeMet.setBody(ast.newBlock());

    // make enum constant values array: new MyEnum() { MYENUMCST1, MYENUMCST2, ... }
    List<CAstNode> eltNodes = new ArrayList<>(constants.size() + 1);
    TypeReference arrayTypeRef = fIdentityMapper.getTypeRef(enumType.createArrayType(1));
    eltNodes.add(
        makeNode(
            context,
            fFactory,
            fakeMet,
            CAstNode.NEW,
            fFactory.makeConstant(arrayTypeRef),
            fFactory.makeConstant(constants.size())));
    for (IVariableBinding cst : constants)
      eltNodes.add(
          createFieldAccess(
              makeNode(context, fFactory, fakeMet, CAstNode.VOID),
              cst.getName(),
              cst,
              fakeMet,
              context));

    CAstNode bodyNode =
        makeNode(
            context,
            fFactory,
            fakeMet,
            CAstNode.LOCAL_SCOPE,
            makeNode(
                context,
                fFactory,
                fakeMet,
                CAstNode.BLOCK_STMT,
                makeNode(
                    context,
                    fFactory,
                    fakeMet,
                    CAstNode.RETURN,
                    makeNode(context, fFactory, fakeMet, CAstNode.ARRAY_LITERAL, eltNodes))));

    List<CAstType> paramTypes = new ArrayList<>(0);
    return new ProcedureEntity(
        bodyNode,
        fakeMet,
        enumType,
        memberEntities,
        context,
        paramTypes,
        enumType.createArrayType(1),
        met.getModifiers(),
        handleAnnotations(enumType),
        null);
  }

  private void doEnumHiddenEntities(
      ITypeBinding typeBinding, List<CAstEntity> memberEntities, WalkContext context) {
    // PART I: create a $VALUES field
    // collect constants
    // ArrayList<String> constants = new ArrayList<String>();
    // for ( ASTNode n: staticInits )
    // if ( n instanceof EnumConstantDeclaration )
    // constants.add(((EnumConstantDeclaration)n).getName().getIdentifier());
    // figure out a suitable untaken name
    // String hiddenFieldName = "hidden values field"; // illegal name
    // // public static final MyEnum[] $VALUES;
    // memberEntities.add(new FieldEntity(hiddenFieldName,
    // typeBinding.createArrayType(1), enumQuals,
    // makePosition(-1,-1)));
    //
    // EnumConstantDeclaration fakeValuesDecl = ast.newEnumConstantDeclaration();
    // // pass along values that we will use in createEnumConstantDeclarationInit() in creating
    // static initializer
    // fakeValuesDecl.setProperty("com.ibm.wala.cast.java.translator.jdt.fakeValuesDeclName",
    // hiddenFieldName);
    // fakeValuesDecl.setProperty("com.ibm.wala.cast.java.translator.jdt.fakeValuesDeclConstants",
    // constants);
    // staticInits.add(fakeValuesDecl);

    ArrayList<IVariableBinding> constants = new ArrayList<>();
    for (IVariableBinding var : typeBinding.getDeclaredFields())
      if (var.isEnumConstant()) constants.add(var);

    // constants are unsorted by default
    constants.sort(Comparator.comparingInt(IVariableBinding::getVariableId));

    // PART II: create values()
    memberEntities.add(createEnumValuesMethod(typeBinding, constants, context));

    // PART III: create valueOf()
    memberEntities.add(createEnumValueOfMethod(typeBinding, context));
  }

  private CAstEntity visit(EnumDeclaration n, WalkContext context) {
    // JDT contains correct type info / class / subclass info for the enum
    return createClassDeclaration(
        n,
        n.bodyDeclarations(),
        n.enumConstants(),
        n.resolveBinding(),
        n.getName().getIdentifier(),
        n.resolveBinding().getModifiers(),
        false,
        false,
        context,
        makePosition(n.getName()));
  }

  /**
   * @param n for positioning.
   */
  private CAstEntity createEnumConstructorWithParameters(
      IMethodBinding ctor,
      ASTNode n,
      WalkContext oldContext,
      ArrayList<ASTNode> inits,
      MethodDeclaration nonDefaultCtor) {
    // PART I: find super ctor to call
    ITypeBinding newType = ctor.getDeclaringClass();
    ITypeBinding javalangenumType = newType.getSuperclass();
    IMethodBinding superCtor = null;

    if (newType.isEnum()) {
      for (IMethodBinding met : javalangenumType.getDeclaredMethods())
        if (met.isConstructor()) {
          superCtor = met;
          break;
        }
    }

    assert superCtor != null : "enum";

    // PART II: make ctor with simply "super(a,b,c...)"
    // TODO: extra CAstNodes
    final Map<CAstNode, CAstEntity> memberEntities = new LinkedHashMap<>();
    final MethodContext context = new MethodContext(oldContext, memberEntities);
    MethodDeclaration fakeCtor = ast.newMethodDeclaration();
    fakeCtor.setConstructor(true);
    fakeCtor.setSourceRange(n.getStartPosition(), n.getLength());
    fakeCtor.setBody(ast.newBlock());

    // PART IIa: make a fake JDT constructor method with the proper number of args
    // Make fake args that will be passed
    String[] fakeArguments = new String[3 + ctor.getParameterTypes().length];
    if (nonDefaultCtor == null) {
      for (int i = 3; i < fakeArguments.length; i++)
        fakeArguments[i] =
            "__wala_jdtcast_argument"
                + i; // this is in the case of an anonymous class with parameters, eg NORTH in
      // the following example: public enum A { NORTH("south") { ...} A(String
      // s){} }
    } else {
      for (int i = 3; i < fakeArguments.length; i++)
        fakeArguments[i] =
            ((SingleVariableDeclaration) nonDefaultCtor.parameters().get(i - 3))
                .getName()
                .getIdentifier();
    }

    ArrayList<CAstType> paramTypes = new ArrayList<>(superCtor.getParameterTypes().length);
    fakeArguments[0] = "this";
    fakeArguments[1] =
        "__wala_jdtcast_argument1"; // TODO FIXME: change to invalid name in the case that
    // nonDefaultCtor != null
    fakeArguments[2] =
        "__wala_jdtcast_argument2"; // otherwise there will be conflicts if we name our variable
    // __wala_jdtcast_argument1!!!
    for (int i = 1; i < fakeArguments.length; i++) {
      // the name
      SingleVariableDeclaration svd = ast.newSingleVariableDeclaration();
      svd.setName(ast.newSimpleName(fakeArguments[i]));
      fakeCtor.parameters().add(svd);

      // the type
      switch (i) {
        case 1:
          paramTypes.add(fTypeDict.getCAstTypeFor(ast.resolveWellKnownType("java.lang.String")));
          break;
        case 2:
          paramTypes.add(fTypeDict.getCAstTypeFor(ast.resolveWellKnownType("int")));
          break;
        default:
          paramTypes.add(fTypeDict.getCAstTypeFor(ctor.getParameterTypes()[i - 3]));
          break;
      }
    }

    // PART IIb: create the statements in the constructor
    // one super() call plus the inits
    List<CAstNode> bodyNodes;
    if (nonDefaultCtor == null) bodyNodes = new ArrayList<>(inits.size() + 1);
    else bodyNodes = new ArrayList<>(inits.size() + 2);

    // make super(...) call
    // this, call ref, args
    List<CAstNode> children;
    if (ctor.isDefaultConstructor())
      children =
          new ArrayList<>(
              4
                  + ctor.getParameterTypes()
                      .length); // anonymous class' implicit constructors call constructors with
    // more than standard two enum args
    else children = new ArrayList<>(4); // explicit constructor
    children.add(makeNode(context, fFactory, n, CAstNode.SUPER));
    CallSiteReference callSiteRef =
        CallSiteReference.make(
            0, fIdentityMapper.getMethodRef(superCtor), IInvokeInstruction.Dispatch.SPECIAL);
    children.add(fFactory.makeConstant(callSiteRef));
    children.add(
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.VAR,
            fFactory.makeConstant(fakeArguments[1]),
            fFactory.makeConstant(paramTypes.get(0))));
    children.add(
        makeNode(
            context,
            fFactory,
            n,
            CAstNode.VAR,
            fFactory.makeConstant(fakeArguments[2]),
            fFactory.makeConstant(paramTypes.get(1))));

    if (ctor.isDefaultConstructor())
      for (int i = 0; i < ctor.getParameterTypes().length; i++)
        children.add(
            makeNode(
                context,
                fFactory,
                n,
                CAstNode.VAR,
                fFactory.makeConstant(fakeArguments[i + 3]),
                fFactory.makeConstant(paramTypes.get(i + 2))));

    bodyNodes.add(makeNode(context, fFactory, n, CAstNode.CALL, children));
    // QUESTION: no handleExceptions?

    for (ASTNode init : inits) bodyNodes.add(visitFieldInitNode(init, context));

    if (nonDefaultCtor != null) bodyNodes.add(visitNode(nonDefaultCtor.getBody(), context));

    // finally, make the procedure entity
    CAstNode ast = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, bodyNodes);
    return new ProcedureEntity(
        ast, fakeCtor, newType, memberEntities, context, paramTypes, null, handleAnnotations(ctor));
  }
}
